diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF index 7da966d03d..103af04498 100644 --- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF @@ -40,6 +40,7 @@ Export-Package: io.cloudbeaver, io.cloudbeaver.model.utils, io.cloudbeaver.registry, io.cloudbeaver.server, + io.cloudbeaver.server.filters, io.cloudbeaver.service, io.cloudbeaver.service.admin, io.cloudbeaver.service.security, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/filters/ServerConfigurationTimeLimitFilter.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/filters/ServerConfigurationTimeLimitFilter.java new file mode 100644 index 0000000000..d669e8ae32 --- /dev/null +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/server/filters/ServerConfigurationTimeLimitFilter.java @@ -0,0 +1,64 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2025 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.cloudbeaver.server.filters; + +import graphql.execution.instrumentation.InstrumentationContext; +import graphql.execution.instrumentation.InstrumentationState; +import graphql.execution.instrumentation.SimpleInstrumentationContext; +import graphql.execution.instrumentation.SimplePerformantInstrumentation; +import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters; +import graphql.validation.ValidationError; +import io.cloudbeaver.model.app.ServletApplication; +import org.jkiss.dbeaver.Log; + +import java.time.Duration; +import java.util.List; + +public class ServerConfigurationTimeLimitFilter extends SimplePerformantInstrumentation { + private static final Log log = Log.getLog(ServerConfigurationTimeLimitFilter.class); + + private static final int MINUTES_OF_INACTION_BEFORE_DISABLING_REQUEST_PROCESSING = 60; + private final ServletApplication application; + + public ServerConfigurationTimeLimitFilter(ServletApplication application) { + this.application = application; + } + + @Override + public InstrumentationContext> beginValidation( + InstrumentationValidationParameters parameters, + InstrumentationState state + ) { + boolean isOutOfTime = System.currentTimeMillis() - application.getApplicationStartTime() + > Duration.ofMinutes(MINUTES_OF_INACTION_BEFORE_DISABLING_REQUEST_PROCESSING).toMillis(); + if (application.isConfigurationMode() && isOutOfTime) { + log.warn("Server configuration time has expired. A server restart is required to continue."); + ValidationError error = ValidationError.newValidationError() + .description("Server configuration time has expired. A server restart is required to continue.") + .build(); + return new SimpleInstrumentationContext<>() { + @Override + public void onCompleted(List result, Throwable t) { + result.clear(); + result.add(error); + } + }; + } + return super.beginValidation(parameters, state); + } +} diff --git a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java index 8a59df8ee8..cba73e94b0 100644 --- a/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java +++ b/server/bundles/io.cloudbeaver.server.ce/src/io/cloudbeaver/server/jetty/CBJettyServer.java @@ -1,6 +1,6 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp and others + * Copyright (C) 2010-2025 DBeaver Corp and others * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBConstants; import io.cloudbeaver.server.graphql.GraphQLEndpoint; +import io.cloudbeaver.server.filters.ServerConfigurationTimeLimitFilter; import io.cloudbeaver.server.servlets.CBImageServlet; import io.cloudbeaver.server.servlets.CBStaticServlet; import io.cloudbeaver.server.servlets.WebStatusServlet; @@ -115,7 +116,13 @@ public class CBJettyServer { servletContextHandler.addServlet(new ServletHolder("status", new WebStatusServlet()), "/status"); - servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), serverConfiguration.getServicesURI() + "gql/*"); + servletContextHandler.addServlet( + new ServletHolder( + "graphql", + new GraphQLEndpoint(new ServerConfigurationTimeLimitFilter(application)) + ), + serverConfiguration.getServicesURI() + "gql/*" + ); servletContextHandler.addEventListener(new CBServerContextListener(application)); // Add extensions from services diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java index 1dacb5c26d..a4e09dac9f 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java @@ -19,7 +19,7 @@ package io.cloudbeaver.server.graphql; import com.google.gson.*; import graphql.*; import graphql.execution.*; -import graphql.execution.instrumentation.SimplePerformantInstrumentation; +import graphql.execution.instrumentation.Instrumentation; import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; import graphql.schema.GraphQLSchema; @@ -51,7 +51,10 @@ import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; public class GraphQLEndpoint extends HttpServlet { @@ -73,13 +76,13 @@ public class GraphQLEndpoint extends HttpServlet { .create(); private GraphQLBindingContext bindingContext; - public GraphQLEndpoint() { + public GraphQLEndpoint(Instrumentation instrumentation) { GraphQLSchema schema = buildSchema(); PropertyDataFetcherHelper.setUseLambdaFactory(false); graphQL = GraphQL .newGraphQL(schema) - .instrumentation(new SimplePerformantInstrumentation()) + .instrumentation(instrumentation) .queryExecutionStrategy(new WebExecutionStrategy()) .mutationExecutionStrategy(new WebExecutionStrategy()) .build(); diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts index 186a4c89d4..f2cd0c12a5 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts @@ -157,17 +157,21 @@ export class ConfigurationWizardService { } private async finish() { - for (const step of this.steps) { - if (step?.configurationWizardOptions?.onConfigurationFinish) { - await step.configurationWizardOptions.onConfigurationFinish(); + try { + for (const step of this.steps) { + if (step?.configurationWizardOptions?.onConfigurationFinish) { + await step.configurationWizardOptions.onConfigurationFinish(); + } } - } - this.administrationScreenService.clearItemsState(); - this.administrationScreenService.navigateToRoot(); - this.notificationService.logSuccess({ - title: 'administration_configuration_wizard_finish_success_title', - message: 'administration_configuration_wizard_finish_success_message', - }); + this.administrationScreenService.clearItemsState(); + this.administrationScreenService.navigateToRoot(); + this.notificationService.logSuccess({ + title: 'administration_configuration_wizard_finish_success_title', + message: 'administration_configuration_wizard_finish_success_message', + }); + } catch (exception: any) { + this.notificationService.logException(exception, 'core_administration_configuration_wizard_finish_fail_title'); + } } } diff --git a/webapp/packages/core-administration/src/locales/en.ts b/webapp/packages/core-administration/src/locales/en.ts index cf818b0bad..2f796fa257 100644 --- a/webapp/packages/core-administration/src/locales/en.ts +++ b/webapp/packages/core-administration/src/locales/en.ts @@ -4,4 +4,5 @@ export default [ ['administration_configuration_wizard_title', 'Initial Server Configuration'], ['administration_configuration_wizard_finish_success_title', 'Server configured'], ['administration_configuration_wizard_finish_success_message', 'You can log-in as administrator in order to set up additional parameters.'], + ['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'], ]; diff --git a/webapp/packages/core-administration/src/locales/fr.ts b/webapp/packages/core-administration/src/locales/fr.ts index 1f664cabda..c90f855d58 100644 --- a/webapp/packages/core-administration/src/locales/fr.ts +++ b/webapp/packages/core-administration/src/locales/fr.ts @@ -14,4 +14,5 @@ export default [ 'administration_configuration_wizard_finish_success_message', "Vous pouvez vous connecter en tant qu'administrateur pour configurer des paramètres supplémentaires.", ], + ['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'], ]; diff --git a/webapp/packages/core-administration/src/locales/it.ts b/webapp/packages/core-administration/src/locales/it.ts index e69a357c97..8a9bdddfed 100644 --- a/webapp/packages/core-administration/src/locales/it.ts +++ b/webapp/packages/core-administration/src/locales/it.ts @@ -4,4 +4,5 @@ export default [ ['administration_configuration_wizard_title', 'Configurazione Iniziale del Server'], ['administration_configuration_wizard_finish_success_title', 'Server configured'], ['administration_configuration_wizard_finish_success_message', 'You can log-in as administrator in order to set up additional parameters.'], + ['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'], ]; diff --git a/webapp/packages/core-administration/src/locales/ru.ts b/webapp/packages/core-administration/src/locales/ru.ts index 5e00ac89f0..719918649e 100644 --- a/webapp/packages/core-administration/src/locales/ru.ts +++ b/webapp/packages/core-administration/src/locales/ru.ts @@ -4,4 +4,5 @@ export default [ ['administration_configuration_wizard_title', 'Начальная настройка сервера'], ['administration_configuration_wizard_finish_success_title', 'Сервер настроен'], ['administration_configuration_wizard_finish_success_message', 'Вы можете войти администратором чтобы настроить дополнительные параметры.'], + ['core_administration_configuration_wizard_finish_fail_title', 'Не удалось настроить сервер'], ]; diff --git a/webapp/packages/core-administration/src/locales/zh.ts b/webapp/packages/core-administration/src/locales/zh.ts index 7b8cc21cf1..8f2d31b833 100644 --- a/webapp/packages/core-administration/src/locales/zh.ts +++ b/webapp/packages/core-administration/src/locales/zh.ts @@ -11,4 +11,5 @@ export default [ ['administration_configuration_wizard_title', '初始化服务器配置'], ['administration_configuration_wizard_finish_success_title', '服务器已完成配置'], ['administration_configuration_wizard_finish_success_message', '您可以使用管理员登录后进行更多参数配置。'], + ['core_administration_configuration_wizard_finish_fail_title', 'Failed to configure server'], ]; diff --git a/webapp/packages/core-ui/src/Form/FormPart.ts b/webapp/packages/core-ui/src/Form/FormPart.ts index 995a22626d..9b8eb4ba49 100644 --- a/webapp/packages/core-ui/src/Form/FormPart.ts +++ b/webapp/packages/core-ui/src/Form/FormPart.ts @@ -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. @@ -40,7 +40,7 @@ export abstract class FormPart implements IFormPar this.formState.submitTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.save.bind(this))); this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.format.bind(this))); - this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.validate.bind(this))); + this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.handleValidation.bind(this))); makeObservable(this, { initialState: observable, @@ -146,6 +146,15 @@ export abstract class FormPart implements IFormPar this.setState(toJS(this.initialState)); } + private async handleValidation(data: IFormState, contexts: IExecutionContextProvider>): Promise { + try { + this.exception = null; + await this.validate(data, contexts); + } catch (exception: any) { + this.exception = exception; + } + } + protected setInitialState(initialState: TPartState) { this.initialState = initialState; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts index a40c2cbd34..4f81c024f7 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts @@ -11,6 +11,7 @@ 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 { getFirstException } from '@cloudbeaver/core-utils'; import { ADMINISTRATION_SERVER_CONFIGURATION_ITEM } from './ServerConfiguration/ADMINISTRATION_SERVER_CONFIGURATION_ITEM.js'; import { ServerConfigurationFormStateManager } from './ServerConfiguration/ServerConfigurationFormStateManager.js'; @@ -71,8 +72,20 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap { return true; }, onConfigurationFinish: async () => { - await this.serverConfigurationFormStateManager.formState?.save(); - await this.sessionDataResource.refresh(); + const state = this.serverConfigurationFormStateManager.formState; + if (state) { + const saved = await state.save(); + + if (!saved) { + const error = getFirstException(state.exception); + + if (error) { + throw getFirstException(error); + } + } + + await this.sessionDataResource.refresh(); + } }, onLoad: () => { this.serverConfigurationFormStateManager.create();