diff --git a/webapp/packages/core-authentication/src/ADMIN_USERNAME_MIN_LENGTH.ts b/webapp/packages/core-authentication/src/ADMIN_USERNAME_MIN_LENGTH.ts new file mode 100644 index 0000000000..fff36b1d12 --- /dev/null +++ b/webapp/packages/core-authentication/src/ADMIN_USERNAME_MIN_LENGTH.ts @@ -0,0 +1,9 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 const ADMIN_USERNAME_MIN_LENGTH = 6; diff --git a/webapp/packages/core-authentication/src/index.ts b/webapp/packages/core-authentication/src/index.ts index abb2a0e971..5305ceadce 100644 --- a/webapp/packages/core-authentication/src/index.ts +++ b/webapp/packages/core-authentication/src/index.ts @@ -33,3 +33,4 @@ export * from './AUTH_SETTINGS_GROUP.js'; export * from './PasswordPolicyService.js'; export * from './TeamRolesResource.js'; export * from './TeamInfoMetaParametersResource.js'; +export * from './ADMIN_USERNAME_MIN_LENGTH.js'; diff --git a/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts b/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts index 3acf3c0c2a..cfe6b6753f 100644 --- a/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts +++ b/webapp/packages/core-root/src/DefaultNavigatorSettingsResource.ts @@ -5,8 +5,6 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { action, makeObservable, observable } from 'mobx'; - import { injectable } from '@cloudbeaver/core-di'; import { CachedDataResource } from '@cloudbeaver/core-resource'; import { type DefaultNavigatorSettingsFragment, GraphQLService, type NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; @@ -18,86 +16,24 @@ export type DefaultNavigatorSettings = DefaultNavigatorSettingsFragment['default @injectable() export class DefaultNavigatorSettingsResource extends CachedDataResource { - update: NavigatorSettingsInput; - constructor( private readonly graphQLService: GraphQLService, serverConfigResource: ServerConfigResource, ) { super(() => null, undefined, []); this.sync(serverConfigResource); - - this.update = getDefaultNavigatorSettings(); - - makeObservable(this, { - update: observable, - unlinkUpdate: action, - syncUpdateData: action, - }); - } - - isChanged(): boolean { - if (!this.data) { - return false; - } - - return !isNavigatorViewSettingsEqual(this.data, this.update); - } - - setDataUpdate(update: NavigatorSettingsInput): void { - this.update = update; - } - - resetUpdate(): void { - if (this.data) { - this.syncUpdateData(this.data); - } } - unlinkUpdate(): void { - if (this.data) { - Object.assign(this.update, this.data); - } else { - this.update = getDefaultNavigatorSettings(); - } - } - - async save(): Promise { - await this.performUpdate( - undefined, - undefined, - async () => { - await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: this.update }); - - this.setData(await this.loader()); - - this.onDataOutdated.execute(); - }, - () => !this.isChanged(), - ); + async save(settings: NavigatorSettingsInput): Promise { + await this.performUpdate(undefined, undefined, async () => { + await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings }); + this.setData(await this.loader()); + this.onDataOutdated.execute(); + }); } protected async loader(): Promise { const { defaultNavigatorSettings } = await this.graphQLService.sdk.getDefaultNavigatorSettings(); - - this.syncUpdateData(defaultNavigatorSettings.defaultNavigatorSettings); - return defaultNavigatorSettings.defaultNavigatorSettings; } - - private syncUpdateData(defaultNavigatorSettings: DefaultNavigatorSettings) { - Object.assign(this.update, defaultNavigatorSettings); - } -} - -function getDefaultNavigatorSettings(): NavigatorSettingsInput { - return { - hideFolders: false, - hideSchemas: false, - hideVirtualModel: false, - mergeEntities: false, - showOnlyEntities: false, - showSystemObjects: false, - showUtilityObjects: false, - }; } diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 2d37cd4a2b..0f65d8234e 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -5,8 +5,6 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ -import { action, makeObservable, observable } from 'mobx'; - import { injectable } from '@cloudbeaver/core-di'; import { CachedDataResource } from '@cloudbeaver/core-resource'; import { GraphQLService, type ServerConfigFragment, type ServerConfigInput } from '@cloudbeaver/core-sdk'; @@ -22,8 +20,6 @@ export type ServerConfig = ServerConfigFragment; @injectable() export class ServerConfigResource extends CachedDataResource { - update: ServerConfigInput; - private readonly syncQueue: DataSynchronizationQueue; constructor( @@ -38,13 +34,6 @@ export class ServerConfigResource extends CachedDataResource(this, { - update: observable, - unlinkUpdate: action, - syncUpdateData: action, - }); serverConfigEventHandler.on( () => { @@ -89,27 +78,27 @@ export class ServerConfigResource extends CachedDataResource { await this.graphQLService.sdk.updateProductConfiguration({ configuration }); @@ -169,67 +120,19 @@ export class ServerConfigResource extends CachedDataResource { - await this.performUpdate( - undefined, - undefined, - async () => { - await this.graphQLService.sdk.configureServer({ - configuration: this.update, - }); - this.setData(await this.loader()); - this.onDataOutdated.execute(); - }, - () => !this.isChanged(), - ); - } - - async finishConfiguration(onlyRestart = false): Promise { - await this.performUpdate( - undefined, - undefined, - async () => { - await this.graphQLService.sdk.configureServer({ - configuration: !this.isChanged() && onlyRestart ? {} : this.update, - }); + async save(configuration: ServerConfigInput): Promise { + await this.performUpdate(undefined, undefined, async () => { + await this.graphQLService.sdk.configureServer({ + configuration, + }); - this.setData(await this.loader()); - this.onDataOutdated.execute(); - }, - () => !this.isChanged() && !onlyRestart, - ); + this.setData(await this.loader()); + this.onDataOutdated.execute(); + }); } protected async loader(): Promise { const { serverConfig } = await this.graphQLService.sdk.serverConfig(); - - this.syncUpdateData(serverConfig); - return serverConfig; } - - private syncUpdateData(serverConfig: ServerConfig) { - if (serverConfig.configurationMode) { - return; - } - - this.update.serverName = serverConfig.name; - this.update.serverURL = serverConfig.serverURL; - this.update.sessionExpireTime = serverConfig.sessionExpireTime; - - this.update.adminName = undefined; - this.update.adminPassword = undefined; - - this.update.anonymousAccessEnabled = serverConfig.anonymousAccessEnabled; - - this.update.adminCredentialsSaveEnabled = serverConfig.adminCredentialsSaveEnabled; - this.update.publicCredentialsSaveEnabled = serverConfig.publicCredentialsSaveEnabled; - - this.update.resourceManagerEnabled = serverConfig.resourceManagerEnabled; - - this.update.customConnectionsEnabled = serverConfig.supportsCustomConnections; - this.update.enabledAuthProviders = [...serverConfig.enabledAuthProviders]; - this.update.enabledFeatures = [...serverConfig.enabledFeatures]; - this.update.disabledDrivers = [...serverConfig.disabledDrivers]; - } } diff --git a/webapp/packages/plugin-administration/package.json b/webapp/packages/plugin-administration/package.json index e5d3731730..bc3d449104 100644 --- a/webapp/packages/plugin-administration/package.json +++ b/webapp/packages/plugin-administration/package.json @@ -22,6 +22,7 @@ "@cloudbeaver/core-authentication": "^0", "@cloudbeaver/core-blocks": "^0", "@cloudbeaver/core-connections": "^0", + "@cloudbeaver/core-data-context": "^0", "@cloudbeaver/core-di": "^0", "@cloudbeaver/core-dialogs": "^0", "@cloudbeaver/core-events": "^0", diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts index 726e67ad01..a40c2cbd34 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts @@ -6,10 +6,14 @@ * you may not use this file except in compliance with the License. */ import { AdministrationItemService, AdministrationItemType, ConfigurationWizardService } from '@cloudbeaver/core-administration'; -import { importLazyComponent } from '@cloudbeaver/core-blocks'; +import { ConfirmationDialog, importLazyComponent } from '@cloudbeaver/core-blocks'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; +import { SessionDataResource } from '@cloudbeaver/core-root'; +import { formValidationContext } from '@cloudbeaver/core-ui'; import { ADMINISTRATION_SERVER_CONFIGURATION_ITEM } from './ServerConfiguration/ADMINISTRATION_SERVER_CONFIGURATION_ITEM.js'; +import { ServerConfigurationFormStateManager } from './ServerConfiguration/ServerConfigurationFormStateManager.js'; import { ServerConfigurationService } from './ServerConfiguration/ServerConfigurationService.js'; const FinishPage = importLazyComponent(() => import('./Finish/FinishPage.js').then(m => m.FinishPage)); @@ -28,7 +32,10 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap { constructor( private readonly administrationItemService: AdministrationItemService, private readonly configurationWizardService: ConfigurationWizardService, + private readonly serverConfigurationFormStateManager: ServerConfigurationFormStateManager, + private readonly commonDialogService: CommonDialogService, private readonly serverConfigurationService: ServerConfigurationService, + private readonly sessionDataResource: SessionDataResource, ) { super(); } @@ -50,15 +57,51 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap { configurationWizardOptions: { description: 'administration_configuration_wizard_configuration_step_description', order: 1.5, - onLoad: this.serverConfigurationService.loadConfig.bind(this.serverConfigurationService), - isDone: this.serverConfigurationService.isDone.bind(this.serverConfigurationService), - onFinish: this.serverConfigurationService.saveConfiguration.bind(this.serverConfigurationService, false), - onConfigurationFinish: this.serverConfigurationService.saveConfiguration.bind(this.serverConfigurationService, true), + isDone: () => this.serverConfigurationService.isDone, + onFinish: async () => { + const state = this.serverConfigurationFormStateManager.formState; + + if (state) { + const contexts = await state.validationTask.execute(state); + const validation = contexts.getContext(formValidationContext); + + return validation.valid; + } + + return true; + }, + onConfigurationFinish: async () => { + await this.serverConfigurationFormStateManager.formState?.save(); + await this.sessionDataResource.refresh(); + }, + onLoad: () => { + this.serverConfigurationFormStateManager.create(); + }, }, order: 2, - onActivate: () => this.serverConfigurationService.activate(), - onDeActivate: this.serverConfigurationService.deactivate.bind(this.serverConfigurationService), - onLoad: this.serverConfigurationService.loadConfig.bind(this.serverConfigurationService, false), + onLoad: () => { + this.serverConfigurationFormStateManager.create(); + }, + canDeActivate: async configurationWizard => { + const state = this.serverConfigurationFormStateManager.formState; + + if (state?.isSaving) { + return false; + } + + if (!configurationWizard && state?.isChanged) { + const result = await this.commonDialogService.open(ConfirmationDialog, { + title: 'ui_save_reminder', + message: 'ui_are_you_sure', + }); + + if (result === DialogueStateResult.Rejected) { + return false; + } + } + + return true; + }, getContentComponent: () => ServerConfigurationPage, getDrawerComponent: () => ServerConfigurationDrawerItem, }); diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/MIN_SESSION_EXPIRE_TIME.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/MIN_SESSION_EXPIRE_TIME.ts new file mode 100644 index 0000000000..e78d617cb1 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/MIN_SESSION_EXPIRE_TIME.ts @@ -0,0 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { SESSION_EXPIRE_MIN_TIME, SESSION_TOUCH_TIME_PERIOD } from '@cloudbeaver/core-root'; + +export const MIN_SESSION_EXPIRE_TIME = Math.ceil((SESSION_EXPIRE_MIN_TIME + SESSION_TOUCH_TIME_PERIOD) / 60000) + 1; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx index 5252458780..5e362b1fa6 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx @@ -8,19 +8,19 @@ import { observer } from 'mobx-react-lite'; import { Group, GroupTitle, InputField, useResource, useTranslate } from '@cloudbeaver/core-blocks'; -import { ServerConfigResource, SESSION_EXPIRE_MIN_TIME, SESSION_TOUCH_TIME_PERIOD } from '@cloudbeaver/core-root'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; import type { IServerConfigurationPageState } from '../IServerConfigurationPageState.js'; +import { MIN_SESSION_EXPIRE_TIME } from './MIN_SESSION_EXPIRE_TIME.js'; interface Props { state: IServerConfigurationPageState; } -const INPUT_MIN_SESSION_EXPIRE_TIME = Math.ceil((SESSION_EXPIRE_MIN_TIME + SESSION_TOUCH_TIME_PERIOD) / 60000) + 1; - export const ServerConfigurationInfoForm = observer(function ServerConfigurationInfoForm({ state }) { const serverConfigLoader = useResource(ServerConfigurationInfoForm, ServerConfigResource, undefined); const translate = useTranslate(); + return ( {translate('administration_configuration_wizard_configuration_server_info')} @@ -43,8 +43,8 @@ export const ServerConfigurationInfoForm = observer(function ServerConfig type="number" name="sessionExpireTime" state={state.serverConfig} - min={INPUT_MIN_SESSION_EXPIRE_TIME} - mapState={(v: number | undefined) => String((v === 0 ? 60000 : v ?? 1800000) / 1000 / 60)} + min={MIN_SESSION_EXPIRE_TIME} + mapState={(v: number | undefined) => String((v === 0 ? 60000 : (v ?? 1800000)) / 1000 / 60)} mapValue={(v?: string) => (v === undefined ? 30 : Number(v) || 1) * 1000 * 60} required tiny diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts new file mode 100644 index 0000000000..0000d88ce9 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/IServerConfigurationFormPartState.ts @@ -0,0 +1,40 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { schema } from '@cloudbeaver/core-utils'; + +const ServerConfigurationFormPartStateConfigSchema = schema.object({ + adminCredentialsSaveEnabled: schema.boolean().optional(), + adminName: schema.string().optional(), + adminPassword: schema.string().optional(), + anonymousAccessEnabled: schema.boolean().optional(), + authenticationEnabled: schema.boolean().optional(), + customConnectionsEnabled: schema.boolean().optional(), + disabledDrivers: schema.array(schema.string()).optional(), + enabledAuthProviders: schema.array(schema.string()).optional(), + enabledFeatures: schema.array(schema.string()).optional(), + publicCredentialsSaveEnabled: schema.boolean().optional(), + resourceManagerEnabled: schema.boolean().optional(), + serverName: schema.string().optional(), + serverURL: schema.string().optional(), + sessionExpireTime: schema.number().optional(), +}); + +const ServerConfigurationFormPartStateNavigatorSchema = schema.object({ + hideFolders: schema.boolean(), + hideSchemas: schema.boolean(), + hideVirtualModel: schema.boolean(), + mergeEntities: schema.boolean(), + showOnlyEntities: schema.boolean(), + showSystemObjects: schema.boolean(), + showUtilityObjects: schema.boolean(), +}); + +export type IServerConfigurationFormPartState = { + serverConfig: schema.infer; + navigatorConfig: schema.infer; +}; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationDriversForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationDriversForm.tsx index ba7e76c41b..0fb4a35094 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationDriversForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationDriversForm.tsx @@ -36,23 +36,29 @@ export const ServerConfigurationDriversForm = observer(function ServerCon icon: driver!.icon, })); - const handleSelect = useCallback((value: string) => { - if (serverConfig.disabledDrivers && !serverConfig.disabledDrivers.includes(value)) { - serverConfig.disabledDrivers.push(value); - } - }, []); + const handleSelect = useCallback( + (value: string) => { + if (serverConfig.disabledDrivers && !serverConfig.disabledDrivers.includes(value)) { + serverConfig.disabledDrivers.push(value); + } + }, + [serverConfig.disabledDrivers], + ); - const handleRemove = useCallback((id: string) => { - if (!serverConfig.disabledDrivers) { - return; - } + const handleRemove = useCallback( + (id: string) => { + if (!serverConfig.disabledDrivers) { + return; + } - const index = serverConfig.disabledDrivers.indexOf(id); + const index = serverConfig.disabledDrivers.indexOf(id); - if (index !== -1) { - serverConfig.disabledDrivers.splice(index, 1); - } - }, []); + if (index !== -1) { + serverConfig.disabledDrivers.splice(index, 1); + } + }, + [serverConfig.disabledDrivers], + ); return ( diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts new file mode 100644 index 0000000000..f0a03e3e7e --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormPart.ts @@ -0,0 +1,146 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { AdministrationScreenService } from '@cloudbeaver/core-administration'; +import { ADMIN_USERNAME_MIN_LENGTH, AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, PasswordPolicyService } from '@cloudbeaver/core-authentication'; +import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections'; +import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor'; +import { CachedMapAllKey } from '@cloudbeaver/core-resource'; +import { DefaultNavigatorSettingsResource, PasswordPolicyResource, ProductInfoResource, ServerConfigResource } from '@cloudbeaver/core-root'; +import { FormPart, type IFormState } from '@cloudbeaver/core-ui'; +import { isObjectsEqual } from '@cloudbeaver/core-utils'; + +import { MIN_SESSION_EXPIRE_TIME } from './Form/MIN_SESSION_EXPIRE_TIME.js'; +import type { IServerConfigurationFormPartState } from './IServerConfigurationFormPartState.js'; + +function DEFAULT_STATE_GETTER(): IServerConfigurationFormPartState { + return { + serverConfig: { + adminCredentialsSaveEnabled: false, + anonymousAccessEnabled: false, + authenticationEnabled: false, + customConnectionsEnabled: false, + disabledDrivers: [], + enabledAuthProviders: [], + enabledFeatures: [], + publicCredentialsSaveEnabled: false, + resourceManagerEnabled: false, + serverName: '', + serverURL: '', + sessionExpireTime: MIN_SESSION_EXPIRE_TIME * 1000 * 60, + }, + navigatorConfig: { ...DEFAULT_NAVIGATOR_VIEW_SETTINGS }, + }; +} + +export class ServerConfigurationFormPart extends FormPart { + constructor( + formState: IFormState, + private readonly administrationScreenService: AdministrationScreenService, + private readonly serverConfigResource: ServerConfigResource, + private readonly productInfoResource: ProductInfoResource, + private readonly defaultNavigatorSettingsResource: DefaultNavigatorSettingsResource, + private readonly authProvidersResource: AuthProvidersResource, + private readonly passwordPolicyResource: PasswordPolicyResource, + private readonly passwordPolicyService: PasswordPolicyService, + ) { + super(formState, DEFAULT_STATE_GETTER()); + } + + override isOutdated(): boolean { + return super.isOutdated() || this.serverConfigResource.isOutdated() || this.defaultNavigatorSettingsResource.isOutdated(); + } + + override isLoaded(): boolean { + return super.isLoaded() && this.serverConfigResource.isLoaded() && this.defaultNavigatorSettingsResource.isLoaded(); + } + + protected override async validate( + data: IFormState, + contexts: IExecutionContextProvider>, + ) { + if (this.administrationScreenService.isConfigurationMode) { + await this.authProvidersResource.load(CachedMapAllKey); + + if (this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID)) { + await this.passwordPolicyResource.load(); + + const isNameValid = this.state.serverConfig.adminName && this.state.serverConfig.adminName.length >= ADMIN_USERNAME_MIN_LENGTH; + const isPasswordValid = this.passwordPolicyService.validatePassword(this.state.serverConfig.adminPassword ?? ''); + + if (!isNameValid || !isPasswordValid.isValid) { + ExecutorInterrupter.interrupt(contexts); + } + } + } + } + + 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(); + } + } + + protected override async saveChanges() { + if (!isObjectsEqual(this.state.navigatorConfig, this.initialState.navigatorConfig)) { + await this.defaultNavigatorSettingsResource.save(this.state.navigatorConfig); + } + + await this.serverConfigResource.save(this.state.serverConfig); + } + + protected override async loader() { + const [config, productInfo, defaultNavigatorSettings] = await Promise.all([ + this.serverConfigResource.load(), + this.productInfoResource.load(), + this.defaultNavigatorSettingsResource.load(), + ]); + + let adminName: string | undefined; + let adminPassword: string | undefined; + + if (this.administrationScreenService.isConfigurationMode) { + await this.authProvidersResource.load(CachedMapAllKey); + + if (this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID)) { + adminName = 'cbadmin'; + adminPassword = ''; + } + } + + this.setInitialState({ + serverConfig: { + adminName, + adminPassword, + serverName: config?.name || productInfo?.name, + serverURL: this.administrationScreenService.isConfigurationMode && !config?.distributed ? window.location.origin : (config?.serverURL ?? ''), + sessionExpireTime: config?.sessionExpireTime ?? MIN_SESSION_EXPIRE_TIME * 1000 * 60, + adminCredentialsSaveEnabled: config?.adminCredentialsSaveEnabled ?? false, + publicCredentialsSaveEnabled: config?.publicCredentialsSaveEnabled ?? false, + customConnectionsEnabled: config?.supportsCustomConnections ?? false, + disabledDrivers: config?.disabledDrivers ? [...config.disabledDrivers] : [], + enabledAuthProviders: config?.enabledAuthProviders ? [...config.enabledAuthProviders] : [], + anonymousAccessEnabled: config?.anonymousAccessEnabled ?? false, + enabledFeatures: config?.enabledFeatures ? [...config.enabledFeatures] : [], + resourceManagerEnabled: config?.resourceManagerEnabled ?? false, + }, + navigatorConfig: { ...this.state.navigatorConfig, ...defaultNavigatorSettings }, + }); + } +} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormService.ts new file mode 100644 index 0000000000..88b3f0fb87 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormService.ts @@ -0,0 +1,20 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { injectable } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { LocalizationService } from '@cloudbeaver/core-localization'; +import { FormBaseService, type IFormProps } from '@cloudbeaver/core-ui'; + +export type ServerConfigurationFormProps = IFormProps; + +@injectable() +export class ServerConfigurationFormService extends FormBaseService { + constructor(localizationService: LocalizationService, notificationService: NotificationService) { + super(localizationService, notificationService, 'Server Configuration form'); + } +} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormState.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormState.ts new file mode 100644 index 0000000000..8e97b2b811 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormState.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import type { IServiceProvider } from '@cloudbeaver/core-di'; +import { FormState } from '@cloudbeaver/core-ui'; + +import type { ServerConfigurationFormService } from './ServerConfigurationFormService.js'; + +export class ServerConfigurationFormState extends FormState { + constructor(serviceProvider: IServiceProvider, service: ServerConfigurationFormService) { + super(serviceProvider, service, null); + } +} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormStateManager.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormStateManager.ts new file mode 100644 index 0000000000..e59a107637 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormStateManager.ts @@ -0,0 +1,44 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { makeObservable, observable } from 'mobx'; + +import { injectable, IServiceProvider } from '@cloudbeaver/core-di'; + +import { ServerConfigurationFormService } from './ServerConfigurationFormService.js'; +import { ServerConfigurationFormState } from './ServerConfigurationFormState.js'; + +@injectable() +export class ServerConfigurationFormStateManager { + formState: ServerConfigurationFormState | null; + + constructor( + private readonly serviceProvider: IServiceProvider, + private readonly serverConfigurationFormService: ServerConfigurationFormService, + ) { + this.formState = null; + + makeObservable(this, { + formState: observable.ref, + }); + } + + create() { + if (this.formState) { + return this.formState; + } + + this.formState = new ServerConfigurationFormState(this.serviceProvider, this.serverConfigurationFormService); + return this.formState; + } + + destroy() { + if (this.formState) { + this.formState = null; + } + } +} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index a01f481182..c0aa4c56c0 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { type AdministrationItemContentComponent, ConfigurationWizardService } from '@cloudbeaver/core-administration'; +import { type AdministrationItemContentComponent } from '@cloudbeaver/core-administration'; import { ColoredContainer, ConfirmationDialog, @@ -16,71 +16,89 @@ import { Group, GroupItem, GroupTitle, - Loader, Placeholder, s, ToolsAction, ToolsPanel, + useAutoLoad, useFocus, + useForm, useFormValidator, useS, useTranslate, } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs'; -import { DefaultNavigatorSettingsResource, ServerConfigResource } from '@cloudbeaver/core-root'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { getFirstException } from '@cloudbeaver/core-utils'; import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm.js'; import { ServerConfigurationFeaturesForm } from './Form/ServerConfigurationFeaturesForm.js'; import { ServerConfigurationInfoForm } from './Form/ServerConfigurationInfoForm.js'; import { ServerConfigurationNavigatorViewForm } from './Form/ServerConfigurationNavigatorViewForm.js'; import { ServerConfigurationSecurityForm } from './Form/ServerConfigurationSecurityForm.js'; +import { getServerConfigurationFormPart } from './getServerConfigurationFormPart.js'; import { ServerConfigurationDriversForm } from './ServerConfigurationDriversForm.js'; +import { ServerConfigurationFormStateManager } from './ServerConfigurationFormStateManager.js'; import style from './ServerConfigurationPage.module.css'; import { ServerConfigurationService } from './ServerConfigurationService.js'; export const ServerConfigurationPage: AdministrationItemContentComponent = observer(function ServerConfigurationPage({ configurationWizard }) { const translate = useTranslate(); const styles = useS(style); - const [focusedRef, state] = useFocus({ focusFirstChild: true }); - const service = useService(ServerConfigurationService); - const serverConfigResource = useService(ServerConfigResource); - const defaultNavigatorSettingsResource = useService(DefaultNavigatorSettingsResource); + const [focusedRef, ref] = useFocus({ focusFirstChild: true }); + const serverConfigurationService = useService(ServerConfigurationService); const commonDialogService = useService(CommonDialogService); - const configurationWizardService = useService(ConfigurationWizardService); - const changed = serverConfigResource.isChanged() || defaultNavigatorSettingsResource.isChanged(); - useFormValidator(service.validationTask, state.reference); + const notificationService = useService(NotificationService); + const serverConfigurationFormStateManager = useService(ServerConfigurationFormStateManager); + + const formState = serverConfigurationFormStateManager.formState!; + const part = getServerConfigurationFormPart(formState); + + useAutoLoad(ServerConfigurationPage, [part]); + useFormValidator(formState.validationTask, ref.reference); function handleChange() { - service.changed(); + if (configurationWizard) { + serverConfigurationService.setDone(false); + } - if (!service.state.serverConfig.adminCredentialsSaveEnabled) { - service.state.serverConfig.publicCredentialsSaveEnabled = false; + if (!part.state.serverConfig.adminCredentialsSaveEnabled) { + part.state.serverConfig.publicCredentialsSaveEnabled = false; } } - function reset() { - service.loadConfig(true); - } + const changed = part.isChanged; async function save() { - if (configurationWizard) { - await configurationWizardService.next(); - } else { - if (serverConfigResource.isChanged()) { - const result = await commonDialogService.open(ConfirmationDialog, { - title: 'administration_server_configuration_save_confirmation_title', - message: 'administration_server_configuration_save_confirmation_message', - }); - - if (result === DialogueStateResult.Rejected) { - return; - } + if (changed) { + const result = await commonDialogService.open(ConfirmationDialog, { + title: 'administration_server_configuration_save_confirmation_title', + message: 'administration_server_configuration_save_confirmation_message', + }); + + if (result === DialogueStateResult.Rejected) { + return; } - await service.saveConfiguration(true); + } + + const saved = await formState.save(); + + if (!saved) { + const error = getFirstException(part.exception); + if (error) { + notificationService.logException(error, 'administration_configuration_wizard_configuration_save_error'); + return; + } + + notificationService.logError({ title: 'administration_configuration_wizard_configuration_save_error' }); } } + const form = useForm({ + onSubmit: save, + }); + return ( {!configurationWizard && ( @@ -91,7 +109,7 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser icon="admin-save" viewBox="0 0 24 24" disabled={!changed} - onClick={save} + onClick={() => form.submit()} > {translate('ui_processing_save')} @@ -100,7 +118,7 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser icon="admin-cancel" viewBox="0 0 24 24" disabled={!changed} - onClick={reset} + onClick={() => formState.reset()} > {translate('ui_processing_cancel')} @@ -119,27 +137,21 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser )} - - {() => ( - -
- - - - {translate('administration_configuration_wizard_configuration_plugins')} - - - - - - - - - -
-
- )} -
+
+ + + + {translate('administration_configuration_wizard_configuration_plugins')} + + + + + + + + + +
); diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index e94cb92157..e3e849a64f 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -7,334 +7,41 @@ */ import { makeObservable, observable } from 'mobx'; -import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { ActionSnackbar, type ActionSnackbarProps, PlaceholderContainer } from '@cloudbeaver/core-blocks'; -import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections'; +import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable } from '@cloudbeaver/core-di'; -import { ENotificationType, type INotification, NotificationService } from '@cloudbeaver/core-events'; -import { Executor, ExecutorInterrupter, type IExecutor, type IExecutorHandler } from '@cloudbeaver/core-executor'; -import { DefaultNavigatorSettingsResource, ProductInfoResource, ServerConfigResource, SessionDataResource } from '@cloudbeaver/core-root'; +import { formValidationContext } from '@cloudbeaver/core-ui'; -import { ADMINISTRATION_SERVER_CONFIGURATION_ITEM } from './ADMINISTRATION_SERVER_CONFIGURATION_ITEM.js'; -import type { IServerConfigurationPageState } from './IServerConfigurationPageState.js'; +import type { IServerConfigurationFormPartState } from './IServerConfigurationFormPartState.js'; +import { ServerConfigurationFormService } from './ServerConfigurationFormService.js'; export interface IConfigurationPlaceholderProps { configurationWizard: boolean; - state: IServerConfigurationPageState; -} - -export interface IServerConfigSaveData { - state: IServerConfigurationPageState; - configurationWizard: boolean; - finish: boolean; -} - -export interface ILoadConfigData { - state: IServerConfigurationPageState; - reset: boolean; + state: IServerConfigurationFormPartState; } @injectable() export class ServerConfigurationService { - state: IServerConfigurationPageState; - loading: boolean; + isDone: boolean; - readonly loadConfigTask: IExecutor; - readonly prepareConfigTask: IExecutor; - readonly saveTask: IExecutor; - readonly validationTask: IExecutor; readonly configurationContainer: PlaceholderContainer; readonly pluginsContainer: PlaceholderContainer; - private done: boolean; - private stateLinked: boolean; - private unSaveNotification: INotification | null; - - constructor( - private readonly administrationScreenService: AdministrationScreenService, - private readonly serverConfigResource: ServerConfigResource, - private readonly defaultNavigatorSettingsResource: DefaultNavigatorSettingsResource, - private readonly notificationService: NotificationService, - private readonly sessionDataResource: SessionDataResource, - private readonly productInfoResource: ProductInfoResource, - ) { - this.done = false; - this.loading = true; - this.state = serverConfigStateContext(); - - makeObservable(this, { - state: observable, - loading: observable, - done: observable, - }); - - this.stateLinked = false; - this.unSaveNotification = null; - this.loadConfigTask = new Executor(); - this.prepareConfigTask = new Executor(); - this.saveTask = new Executor(); - this.validationTask = new Executor(); + constructor(private readonly serverConfigurationFormService: ServerConfigurationFormService) { + this.isDone = false; this.configurationContainer = new PlaceholderContainer(); this.pluginsContainer = new PlaceholderContainer(); - this.loadConfigTask - .next(this.validationTask, () => this.getSaveData(false)) - .addHandler(() => { - this.loading = true; - }) - .addHandler(this.loadServerConfig) - .addPostHandler(() => { - this.loading = false; - this.showUnsavedNotification(false); - }); - - this.saveTask.before(this.validationTask).before(this.prepareConfigTask).addPostHandler(this.save); - - this.validationTask.addHandler(this.validateForm).addPostHandler(this.ensureValidation); - - this.serverConfigResource.onDataUpdate.addPostHandler(this.showUnsavedNotification.bind(this, false)); - - this.administrationScreenService.activationEvent.addHandler(this.unlinkState.bind(this)); - } - - changed(): void { - this.done = false; - - this.showUnsavedNotification(true); - } - - deactivate(configurationWizard: boolean, outside: boolean, outsideAdminPage: boolean): void { - if (!outsideAdminPage) { - this.showUnsavedNotification(false); - } - } - - async activate(): Promise { - // this.unSaveNotification?.close(true); - await this.loadConfig(); - } - - async loadConfig(reset = false): Promise { - try { - if (!this.stateLinked) { - this.state = this.administrationScreenService.getItemState('server-configuration', serverConfigStateContext); - reset = true; - this.stateLinked = true; - await this.serverConfigResource.load(); - await this.defaultNavigatorSettingsResource.load(); - - this.serverConfigResource.setDataUpdate(this.state.serverConfig); - this.defaultNavigatorSettingsResource.setDataUpdate(this.state.navigatorConfig); - - if (reset) { - this.defaultNavigatorSettingsResource.resetUpdate(); - this.serverConfigResource.resetUpdate(); - } - } - - await this.loadConfigTask.execute({ - state: this.state, - reset, - }); - } catch (exception: any) { - this.notificationService.logException(exception, "Can't load server configuration"); - } - } - - isDone(): boolean { - return this.done; - } - - async saveConfiguration(finish: boolean): Promise { - const contexts = await this.saveTask.execute(this.getSaveData(finish)); - - const validation = contexts.getContext(serverConfigValidationContext); - - return validation.valid; - } - - private readonly loadServerConfig: IExecutorHandler = async (data, contexts) => { - if (!data.reset) { - return; - } - - try { - const config = await this.serverConfigResource.load(); - const productInfo = await this.productInfoResource.load(); - const defaultNavigatorSettings = await this.defaultNavigatorSettingsResource.load(); - - if (!config) { - return; - } - - data.state.serverConfig.serverName = config.name || productInfo?.name; - data.state.serverConfig.serverURL = config.serverURL; - - if (this.administrationScreenService.isConfigurationMode && !config.distributed) { - data.state.serverConfig.serverURL = window.location.origin; - } - - data.state.serverConfig.sessionExpireTime = config.sessionExpireTime; - - data.state.serverConfig.adminCredentialsSaveEnabled = config.adminCredentialsSaveEnabled; - data.state.serverConfig.publicCredentialsSaveEnabled = config.publicCredentialsSaveEnabled; - data.state.serverConfig.customConnectionsEnabled = config.supportsCustomConnections; - data.state.serverConfig.disabledDrivers = [...config.disabledDrivers]; - - Object.assign(data.state.navigatorConfig, defaultNavigatorSettings); - } catch (exception: any) { - ExecutorInterrupter.interrupt(contexts); - this.notificationService.logException(exception, "Can't load server configuration"); - } - }; - - private getSaveData(finish: boolean): IServerConfigSaveData { - return { - state: this.state, - finish, - configurationWizard: this.administrationScreenService.isConfigurationMode, - }; - } - - private readonly save: IExecutorHandler = async (data, contexts) => { - const validation = contexts.getContext(serverConfigValidationContext); - - if (!validation.valid) { - return; - } - - try { - await this.defaultNavigatorSettingsResource.save(); - if (!data.configurationWizard) { - await this.serverConfigResource.save(); - } - - if (data.configurationWizard && data.finish) { - await this.serverConfigResource.finishConfiguration(); - await this.sessionDataResource.refresh(); - } - } catch (exception: any) { - this.notificationService.logException(exception, "Can't save server configuration"); - - throw exception; - } - }; - - private readonly ensureValidation: IExecutorHandler = (data, contexts) => { - const validation = contexts.getContext(serverConfigValidationContext); - - if (!validation.valid) { - ExecutorInterrupter.interrupt(contexts); - - if (validation.messages.length > 0) { - this.notificationService.notify( - { - title: 'administration_configuration_wizard_step_validation_message', - message: validation.messages.join('\n'), - }, - validation.valid ? ENotificationType.Info : ENotificationType.Error, - ); - } - this.done = false; - } else { - this.done = true; - } - }; - - private readonly validateForm: IExecutorHandler = (data, contexts) => { - const validation = contexts.getContext(serverConfigValidationContext); - - if (!this.isFormFilled(data.state)) { - validation.invalidate(); - } - }; - - private isFormFilled(state: IServerConfigurationPageState) { - if (!state.serverConfig.serverName) { - return false; - } - if ((state.serverConfig.sessionExpireTime ?? 0) < 1) { - return false; - } - return true; - } - - private showUnsavedNotification(close: boolean) { - if ( - (!this.serverConfigResource.isChanged() && !this.defaultNavigatorSettingsResource.isChanged()) || - this.administrationScreenService.activeScreen?.item === ADMINISTRATION_SERVER_CONFIGURATION_ITEM - ) { - this.unSaveNotification?.close(true); - return; - } - - if ( - close || - !this.stateLinked || - this.unSaveNotification || - this.administrationScreenService.isConfigurationMode - // || !this.administrationScreenService.isAdministrationPageActive - ) { - return; - } + this.serverConfigurationFormService.onValidate.addHandler((data, contexts) => { + const validation = contexts.getContext(formValidationContext); + this.setDone(validation.valid); + }); - this.unSaveNotification = this.notificationService.customNotification( - () => ActionSnackbar, - { - actionText: 'administration_configuration_wizard_configuration_server_info_unsaved_navigate', - onAction: () => this.administrationScreenService.navigateToItem(ADMINISTRATION_SERVER_CONFIGURATION_ITEM), - }, - { - title: 'administration_configuration_wizard_configuration_server_info_unsaved_title', - message: 'administration_configuration_wizard_configuration_server_info_unsaved_message', - type: ENotificationType.Info, - onClose: () => { - this.unSaveNotification = null; - }, - }, - ); + makeObservable(this, { + isDone: observable.ref, + }); } - private unlinkState(state: boolean): void { - if (state) { - return; - } - - this.unSaveNotification?.close(true); - this.defaultNavigatorSettingsResource.unlinkUpdate(); - this.serverConfigResource.unlinkUpdate(); - this.stateLinked = false; + setDone(value: boolean) { + this.isDone = value; } } - -export interface IValidationStatusContext { - valid: boolean; - messages: string[]; - invalidate: () => void; - info: (message: string) => void; - error: (message: string) => void; -} - -export function serverConfigValidationContext(): IValidationStatusContext { - return { - valid: true, - messages: [], - invalidate() { - this.valid = false; - }, - info(message: string) { - this.messages.push(message); - }, - error(message: string) { - this.messages.push(message); - this.valid = false; - }, - }; -} - -export function serverConfigStateContext(): IServerConfigurationPageState { - return { - navigatorConfig: { ...DEFAULT_NAVIGATOR_VIEW_SETTINGS }, - serverConfig: {}, - }; -} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/getServerConfigurationFormPart.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/getServerConfigurationFormPart.ts new file mode 100644 index 0000000000..fcb6eb8dc4 --- /dev/null +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/getServerConfigurationFormPart.ts @@ -0,0 +1,40 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ +import { AdministrationScreenService } from '@cloudbeaver/core-administration'; +import { AuthProvidersResource, PasswordPolicyService } from '@cloudbeaver/core-authentication'; +import { createDataContext, DATA_CONTEXT_DI_PROVIDER } from '@cloudbeaver/core-data-context'; +import { DefaultNavigatorSettingsResource, PasswordPolicyResource, ProductInfoResource, ServerConfigResource } from '@cloudbeaver/core-root'; +import type { IFormState } from '@cloudbeaver/core-ui'; + +import { ServerConfigurationFormPart } from './ServerConfigurationFormPart.js'; + +const DATA_CONTEXT_SERVER_CONFIGURATION_FORM_PART = createDataContext('Server Configuration form Part'); + +export function getServerConfigurationFormPart(formState: IFormState): ServerConfigurationFormPart { + return formState.getPart(DATA_CONTEXT_SERVER_CONFIGURATION_FORM_PART, context => { + const di = context.get(DATA_CONTEXT_DI_PROVIDER)!; + const administrationScreenService = di.getService(AdministrationScreenService); + const serverConfigResource = di.getService(ServerConfigResource); + const defaultNavigatorSettingsResource = di.getService(DefaultNavigatorSettingsResource); + const productInfoResource = di.getService(ProductInfoResource); + const authProvidersResource = di.getService(AuthProvidersResource); + const passwordPolicyResource = di.getService(PasswordPolicyResource); + const passwordPolicyService = di.getService(PasswordPolicyService); + + return new ServerConfigurationFormPart( + formState, + administrationScreenService, + serverConfigResource, + productInfoResource, + defaultNavigatorSettingsResource, + authProvidersResource, + passwordPolicyResource, + passwordPolicyService, + ); + }); +} diff --git a/webapp/packages/plugin-administration/src/locales/en.ts b/webapp/packages/plugin-administration/src/locales/en.ts index 65fcc6e8bc..610540de9c 100644 --- a/webapp/packages/plugin-administration/src/locales/en.ts +++ b/webapp/packages/plugin-administration/src/locales/en.ts @@ -24,6 +24,7 @@ export default [ ['administration_configuration_wizard_configuration', 'Server Configuration'], ['administration_configuration_wizard_configuration_step_description', 'Main server configuration'], ['administration_configuration_wizard_configuration_title', 'You can configure the main server parameters here.'], + ['administration_configuration_wizard_configuration_save_error', 'Failed to save server configuration'], [ 'administration_configuration_wizard_configuration_message', 'You will be able to add additional services after the server configuration.\n\rAdministrator is a super user who can configure server, set databases connections, manage other users and much more. Please, remember the entered password. It is not possible to recover administrator password automatically.', diff --git a/webapp/packages/plugin-administration/src/locales/fr.ts b/webapp/packages/plugin-administration/src/locales/fr.ts index 7bbeff7d09..2a8bece70c 100644 --- a/webapp/packages/plugin-administration/src/locales/fr.ts +++ b/webapp/packages/plugin-administration/src/locales/fr.ts @@ -24,6 +24,7 @@ export default [ ['administration_configuration_wizard_configuration', 'Configuration du serveur'], ['administration_configuration_wizard_configuration_step_description', 'Configuration principale du serveur'], ['administration_configuration_wizard_configuration_title', 'Vous pouvez configurer ici les paramètres principaux du serveur.'], + ['administration_configuration_wizard_configuration_save_error', 'Failed to save server configuration'], [ 'administration_configuration_wizard_configuration_message', "Vous pourrez ajouter des services supplémentaires après la configuration du serveur.\nL'administrateur est un super utilisateur qui peut configurer le serveur, définir les connexions aux bases de données, gérer les autres utilisateurs et bien plus encore. Veuillez vous souvenir du mot de passe saisi. Il n'est pas possible de récupérer automatiquement le mot de passe de l'administrateur.", diff --git a/webapp/packages/plugin-administration/src/locales/it.ts b/webapp/packages/plugin-administration/src/locales/it.ts index 1f70630398..c61428acab 100644 --- a/webapp/packages/plugin-administration/src/locales/it.ts +++ b/webapp/packages/plugin-administration/src/locales/it.ts @@ -1,3 +1,10 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 default [ ['administration_server_configuration_save_confirmation_title', 'Aggiornamento delle impostazioni del Server'], ['administration_server_configuration_save_confirmation_message', 'Stai per modificare impostazioni critiche. Sei sicuro?'], @@ -17,6 +24,7 @@ export default [ ['administration_configuration_wizard_configuration', 'Configurazione del Server'], ['administration_configuration_wizard_configuration_step_description', 'Configurazione del server principale'], ['administration_configuration_wizard_configuration_title', 'Puoi configurare i parametri del server principale qui.'], + ['administration_configuration_wizard_configuration_save_error', 'Failed to save server configuration'], [ 'administration_configuration_wizard_configuration_message', "L'amministratore è un super utente che può configurare server, impostare connessioni ai database, gestire altri utenti e molto di più. Si prega di ricordare la password inserita: non sarà possibile recuperarla in maniera automatica.", diff --git a/webapp/packages/plugin-administration/src/locales/ru.ts b/webapp/packages/plugin-administration/src/locales/ru.ts index 1a506514bd..08a2829fb7 100644 --- a/webapp/packages/plugin-administration/src/locales/ru.ts +++ b/webapp/packages/plugin-administration/src/locales/ru.ts @@ -1,8 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2024 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 default [ ['administration_server_configuration_save_confirmation_title', 'Изменение настроек сервера'], ['administration_server_configuration_save_confirmation_message', 'Будут изменены критичные настройки. Вы уверены?'], ['administration_configuration_wizard_configuration', 'Настройки сервера'], + ['administration_configuration_wizard_configuration_save_error', 'Не удалось сохранить конфигурацию сервера'], ['administration_configuration_wizard_configuration_server_info', 'Информация о сервере'], ['administration_configuration_wizard_configuration_server_name', 'Название сервера'], diff --git a/webapp/packages/plugin-administration/src/locales/zh.ts b/webapp/packages/plugin-administration/src/locales/zh.ts index db48bd3a59..b7a36c5e93 100644 --- a/webapp/packages/plugin-administration/src/locales/zh.ts +++ b/webapp/packages/plugin-administration/src/locales/zh.ts @@ -21,6 +21,7 @@ export default [ ['administration_configuration_wizard_configuration', '服务器配置'], ['administration_configuration_wizard_configuration_step_description', '主要的服务器配置'], ['administration_configuration_wizard_configuration_title', '您可以在这里配置主要的服务器参数'], + ['administration_configuration_wizard_configuration_save_error', 'Failed to save server configuration'], [ 'administration_configuration_wizard_configuration_message', '管理员是一个超级用户,可以配置服务器、设置数据库连接、管理其他用户等等。请记住输入的密码。无法自动恢复管理员密码。', diff --git a/webapp/packages/plugin-administration/src/manifest.ts b/webapp/packages/plugin-administration/src/manifest.ts index 83619435e0..4fc56296c3 100644 --- a/webapp/packages/plugin-administration/src/manifest.ts +++ b/webapp/packages/plugin-administration/src/manifest.ts @@ -22,5 +22,7 @@ export const manifest: PluginManifest = { () => import('./AdministrationScreen/AdministrationTopAppBar/AdministrationTopAppBarService.js').then(m => m.AdministrationTopAppBarService), () => import('./AdministrationScreen/ConfigurationWizard/WizardTopAppBar/WizardTopAppBarService.js').then(m => m.WizardTopAppBarService), () => import('./Administration/AdministrationViewService.js').then(m => m.AdministrationViewService), + () => import('./ConfigurationWizard/ServerConfiguration/ServerConfigurationFormStateManager.js').then(m => m.ServerConfigurationFormStateManager), + () => import('./ConfigurationWizard/ServerConfiguration/ServerConfigurationFormService.js').then(m => m.ServerConfigurationFormService), ], }; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx index 679550a8cd..9f6b375839 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx @@ -7,7 +7,8 @@ */ import { observer } from 'mobx-react-lite'; -import { Group, GroupTitle, InputField, useTranslate } from '@cloudbeaver/core-blocks'; +import { ADMIN_USERNAME_MIN_LENGTH } from '@cloudbeaver/core-authentication'; +import { Group, GroupTitle, InputField, usePasswordValidation, useTranslate } from '@cloudbeaver/core-blocks'; import type { ServerConfigInput } from '@cloudbeaver/core-sdk'; interface Props { @@ -16,14 +17,15 @@ interface Props { export const ServerConfigurationAdminForm = observer(function ServerConfigurationAdminForm({ serverConfig }) { const translate = useTranslate(); + const passwordValidationRef = usePasswordValidation(); return ( {translate('administration_configuration_wizard_configuration_admin')} - + {translate('administration_configuration_wizard_configuration_admin_name')} - + {translate('administration_configuration_wizard_configuration_admin_password')} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts deleted file mode 100644 index f7a872a601..0000000000 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2024 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ -import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, PasswordPolicyService } from '@cloudbeaver/core-authentication'; -import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { NotificationService } from '@cloudbeaver/core-events'; -import { ExecutorInterrupter, type IExecutorHandler } from '@cloudbeaver/core-executor'; -import { CachedMapAllKey } from '@cloudbeaver/core-resource'; -import { PasswordPolicyResource, ServerConfigResource } from '@cloudbeaver/core-root'; -import { - type ILoadConfigData, - type IServerConfigSaveData, - ServerConfigurationService, - serverConfigValidationContext, -} from '@cloudbeaver/plugin-administration'; - -@injectable() -export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { - constructor( - private readonly administrationScreenService: AdministrationScreenService, - private readonly serverConfigurationService: ServerConfigurationService, - private readonly authProvidersResource: AuthProvidersResource, - private readonly serverConfigResource: ServerConfigResource, - private readonly notificationService: NotificationService, - private readonly passwordPolicyService: PasswordPolicyService, - private readonly passwordPolicyResource: PasswordPolicyResource, - ) { - super(); - } - - override register(): void { - this.serverConfigurationService.validationTask.addHandler(this.validateForm); - this.serverConfigurationService.loadConfigTask.addHandler(this.loadServerConfig); - } - - private readonly loadServerConfig: IExecutorHandler = async (data, contexts) => { - if (!data.reset) { - return; - } - - try { - const config = await this.serverConfigResource.load(); - - if (!config) { - return; - } - - if (this.administrationScreenService.isConfigurationMode) { - await this.authProvidersResource.load(CachedMapAllKey); - if (this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID)) { - data.state.serverConfig.adminName = 'cbadmin'; - data.state.serverConfig.adminPassword = ''; - } - } else { - data.state.serverConfig.adminName = undefined; - data.state.serverConfig.adminPassword = undefined; - } - - data.state.serverConfig.anonymousAccessEnabled = config.anonymousAccessEnabled; - data.state.serverConfig.enabledAuthProviders = [...config.enabledAuthProviders]; - data.state.serverConfig.enabledFeatures = [...config.enabledFeatures]; - } catch (exception: any) { - ExecutorInterrupter.interrupt(contexts); - this.notificationService.logException(exception, "Can't load server configuration"); - } - }; - - private readonly validateForm: IExecutorHandler = async (data, contexts) => { - await this.authProvidersResource.load(CachedMapAllKey); - const administratorPresented = data.configurationWizard && this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); - - if (!administratorPresented) { - return; - } - - const validation = contexts.getContext(serverConfigValidationContext); - - if (!data.state.serverConfig.adminName || data.state.serverConfig.adminName.length < 6 || !data.state.serverConfig.adminPassword) { - return validation.invalidate(); - } - - await this.passwordPolicyResource.load(); - const passwordValidation = this.passwordPolicyService.validatePassword(data.state.serverConfig.adminPassword); - if (!passwordValidation.isValid) { - validation.error(passwordValidation.errorMessage); - } - }; -} diff --git a/webapp/packages/plugin-authentication-administration/src/manifest.ts b/webapp/packages/plugin-authentication-administration/src/manifest.ts index 7647a55b02..318ae60909 100644 --- a/webapp/packages/plugin-authentication-administration/src/manifest.ts +++ b/webapp/packages/plugin-authentication-administration/src/manifest.ts @@ -18,10 +18,6 @@ export const manifest: PluginManifest = { () => import('./AuthenticationLocaleService.js').then(m => m.AuthenticationLocaleService), () => import('./Administration/Users/UsersTable/CreateUserService.js').then(m => m.CreateUserService), () => import('./Administration/Users/UsersAdministrationNavigationService.js').then(m => m.UsersAdministrationNavigationService), - () => - import('./Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.js').then( - m => m.ServerConfigurationAuthenticationBootstrap, - ), () => import('./Administration/Users/UserForm/AdministrationUserFormService.js').then(m => m.AdministrationUserFormService), () => import('./Administration/Users/Teams/TeamsAdministrationService.js').then(m => m.TeamsAdministrationService), () => import('./Administration/Users/Teams/CreateTeamService.js').then(m => m.CreateTeamService), diff --git a/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts index 9a404a757f..573872a8c0 100644 --- a/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts @@ -6,48 +6,19 @@ * you may not use this file except in compliance with the License. */ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { NotificationService } from '@cloudbeaver/core-events'; -import { ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; -import { type ILoadConfigData, ServerConfigurationService } from '@cloudbeaver/plugin-administration'; +import { ServerConfigurationService } from '@cloudbeaver/plugin-administration'; import { ResourceManagerSettings } from './ResourceManagerSettings.js'; @injectable() export class PluginBootstrap extends Bootstrap { - constructor( - private readonly serverConfigurationService: ServerConfigurationService, - private readonly serverConfigResource: ServerConfigResource, - private readonly notificationService: NotificationService, - ) { + constructor(private readonly serverConfigurationService: ServerConfigurationService) { super(); - - this.loadConfigHandler = this.loadConfigHandler.bind(this); } override register(): void | Promise { this.serverConfigurationService.pluginsContainer.add(ResourceManagerSettings, 0); - this.serverConfigurationService.loadConfigTask.addHandler(this.loadConfigHandler); } override async load(): Promise {} - - private async loadConfigHandler(data: ILoadConfigData, contexts: IExecutionContextProvider) { - if (!data.reset) { - return; - } - - try { - const config = await this.serverConfigResource.load(); - - if (!config) { - return; - } - - data.state.serverConfig.resourceManagerEnabled = config.resourceManagerEnabled; - } catch (exception) { - ExecutorInterrupter.interrupt(contexts); - this.notificationService.logException(exception as any, "Can't load server configuration"); - } - } }