diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1f228a5c84..a9a59d65e4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -50,6 +50,19 @@ "isDefault": true } }, + { + "label": "Run Backend CE", + "type": "shell", + "windows": { + "command": "./run-server.bat" + }, + "osx": { + "command": "./run-server.sh" + }, + "options": { + "cwd": "${workspaceFolder}/deploy/cloudbeaver" + } + }, { "label": "Yarn Install", "type": "shell", diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index 6aad7fe5a0..b57e8ed021 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -443,4 +443,10 @@ public void addCloseListener(DBRRunnableParametrized listener closeListeners.add(listener); } + @Property + public int getKeepAliveInterval() { + return dataSourceContainer.getConnectionConfiguration().getKeepAliveInterval(); + } + + } diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 0b8b0b8a2a..0e998720b4 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -314,6 +314,8 @@ type ConnectionInfo { databaseName: String url: String + keepAliveInterval: Int! + properties: Object template: Boolean! @@ -465,6 +467,9 @@ input ConnectionConfig { # Properties properties: Object + # Keep-Alive interval + keepAliveInterval: Int + # Template connection template: Boolean # Read-onyl connection diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java index dfef45dc1c..d04e25b561 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java @@ -182,6 +182,9 @@ public static void setConnectionConfiguration(DBPDriver driver, DBPConnectionCon if (config.getAuthModelId() != null) { dsConfig.setAuthModelId(config.getAuthModelId()); } + if (config.getKeepAliveInterval() >= 0) { + dsConfig.setKeepAliveInterval(config.getKeepAliveInterval()); + } // Save provider props if (config.getProviderProperties() != null) { dsConfig.setProviderProperties(new LinkedHashMap<>()); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java index b902a72a83..ade7f8c719 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionConfig.java @@ -43,6 +43,8 @@ public class WebConnectionConfig { private String databaseName; private String url; + private int keepAliveInterval; + private String name; private String description; private String folder; @@ -80,6 +82,8 @@ public WebConnectionConfig(Map params) { databaseName = JSONUtils.getString(params, "databaseName"); url = JSONUtils.getString(params, "url"); + keepAliveInterval = JSONUtils.getInteger(params, "keepAliveInterval", -1); + name = JSONUtils.getString(params, "name"); description = JSONUtils.getString(params, "description"); folder = JSONUtils.getString(params, "folder"); @@ -222,4 +226,9 @@ public void setSaveCredentials(boolean saveCredentials) { public Map getProviderProperties() { return providerProperties; } + + @Property + public Integer getKeepAliveInterval() { + return keepAliveInterval; + } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java index 89b441a784..29ba4ff15c 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBPlatform.java @@ -19,6 +19,7 @@ import io.cloudbeaver.auth.NoAuthCredentialsProvider; import io.cloudbeaver.server.jobs.SessionStateJob; +import io.cloudbeaver.server.jobs.WebDataSourceMonitorJob; import io.cloudbeaver.server.jobs.WebSessionMonitorJob; import io.cloudbeaver.service.session.WebSessionManager; import org.eclipse.core.resources.ResourcesPlugin; @@ -165,6 +166,9 @@ protected void initialize() { new SessionStateJob(this) .scheduleMonitor(); + new WebDataSourceMonitorJob(this) + .scheduleMonitor(); + new AbstractJob("Delete temp folder") { @Override protected IStatus run(DBRProgressMonitor monitor) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/WebDataSourceMonitorJob.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/WebDataSourceMonitorJob.java new file mode 100644 index 0000000000..017708b5c4 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jobs/WebDataSourceMonitorJob.java @@ -0,0 +1,42 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 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.jobs; + +import io.cloudbeaver.model.session.BaseWebSession; +import io.cloudbeaver.server.CBPlatform; +import org.jkiss.dbeaver.model.app.DBPPlatform; +import org.jkiss.dbeaver.runtime.jobs.DataSourceMonitorJob; + +import java.util.Collection; + +/** + * Web data source monitor job. + */ +public class WebDataSourceMonitorJob extends DataSourceMonitorJob { + + public WebDataSourceMonitorJob(DBPPlatform platform) { + super(platform); + } + + @Override + protected void doJob() { + Collection allSessions = CBPlatform.getInstance().getSessionManager().getAllActiveSessions(); + allSessions.parallelStream().forEach( + s -> checkDataSourceAliveInWorkspace(s.getWorkspace()) + ); + } +} diff --git a/webapp/packages/core-connections/src/locales/en.ts b/webapp/packages/core-connections/src/locales/en.ts index fb3a42ffcf..e00350c75d 100644 --- a/webapp/packages/core-connections/src/locales/en.ts +++ b/webapp/packages/core-connections/src/locales/en.ts @@ -55,6 +55,8 @@ export default [ ['connections_connection_test_fail', 'Connection test failed'], ['connections_connection_create_fail', 'Fail to create connection'], ['connections_connection_save_fail', 'Fail to save connection'], + ['connections_connection_keep_alive', 'Keep alive (in seconds)'], + ['connections_connection_keep_alive_tooltip', 'No auto disconnect'], ['connections_network_handler_test', 'Test Tunnel'], ['connections_network_handler_test_fail', 'Tunnel test failed'], ['connections_network_handler_test_success', 'Tunnel test success'], diff --git a/webapp/packages/core-connections/src/locales/it.ts b/webapp/packages/core-connections/src/locales/it.ts index 6bef3cfbf9..3debda1203 100644 --- a/webapp/packages/core-connections/src/locales/it.ts +++ b/webapp/packages/core-connections/src/locales/it.ts @@ -53,6 +53,8 @@ export default [ ['connections_connection_test_fail', 'Prova di connessione fallita'], ['connections_connection_create_fail', 'Errore di creazione connessione'], ['connections_connection_save_fail', 'Errore di salvataggio connessione'], + ['connections_connection_keep_alive', 'Keep alive (in seconds)'], + ['connections_connection_keep_alive_tooltip', 'No auto disconnect'], ['connections_network_handler_test', 'Prova il Tunnel'], ['connections_network_handler_test_fail', 'Prova del Tunnel fallita'], ['connections_network_handler_test_success', 'Prova del Tunnel terminata con successo'], diff --git a/webapp/packages/core-connections/src/locales/ru.ts b/webapp/packages/core-connections/src/locales/ru.ts index c117a9b251..81bc0690c3 100644 --- a/webapp/packages/core-connections/src/locales/ru.ts +++ b/webapp/packages/core-connections/src/locales/ru.ts @@ -52,6 +52,8 @@ export default [ ['connections_connection_test_fail', 'Не удалось выполнить подключение'], ['connections_connection_create_fail', 'Не удалось создать подключение'], ['connections_connection_save_fail', 'Не удалось сохранить подключение'], + ['connections_connection_keep_alive', 'Поддерживать соединение (в секундах)'], + ['connections_connection_keep_alive_tooltip', 'Не отключать соединение'], ['connections_network_handler_test', 'Проверить подключение'], ['connections_network_handler_test_fail', 'Не удалось установить соединение'], ['connections_network_handler_test_success', 'Соединение установлено'], diff --git a/webapp/packages/core-connections/src/locales/zh.ts b/webapp/packages/core-connections/src/locales/zh.ts index f996c81b34..e05e608b5f 100644 --- a/webapp/packages/core-connections/src/locales/zh.ts +++ b/webapp/packages/core-connections/src/locales/zh.ts @@ -51,6 +51,8 @@ export default [ ['connections_connection_test_fail', '连接测试失败'], ['connections_connection_create_fail', '无法创建连接'], ['connections_connection_save_fail', '无法保存连接'], + ['connections_connection_keep_alive', 'Keep alive (in seconds)'], + ['connections_connection_keep_alive_tooltip', 'No auto disconnect'], ['connections_network_handler_test', '测试隧道'], ['connections_network_handler_test_fail', '隧道测试失败'], ['connections_network_handler_test_success', '隧道测试成功'], diff --git a/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnection.gql b/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnection.gql index 13d53704ad..f477719d71 100644 --- a/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnection.gql +++ b/webapp/packages/core-sdk/src/queries/fragments/DatabaseConnection.gql @@ -4,6 +4,7 @@ fragment DatabaseConnection on ConnectionInfo { name description driverId + keepAliveInterval template connected diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index 8f1cc2ecc8..e886f75a31 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -229,6 +229,8 @@ export class ConnectionOptionsTabService extends Bootstrap { state.config.saveCredentials = state.info.credentialsSaved; state.config.sharedCredentials = state.info.sharedCredentials; + state.config.keepAliveInterval = state.info.keepAliveInterval; + if (state.info.authProperties) { for (const property of state.info.authProperties) { if (!property.features.includes('password')) { @@ -293,6 +295,8 @@ export class ConnectionOptionsTabService extends Bootstrap { tempConfig.driverId = state.config.driverId; + tempConfig.keepAliveInterval = Number(state.config.keepAliveInterval); + if (!state.config.template && state.config.folder) { tempConfig.folder = state.config.folder; } @@ -407,7 +411,8 @@ export class ConnectionOptionsTabService extends Bootstrap { (config.saveCredentials !== undefined && config.saveCredentials !== data.info.credentialsSaved) || (config.sharedCredentials !== undefined && config.sharedCredentials !== data.info.sharedCredentials) || (config.providerProperties !== undefined && - !isObjectPropertyInfoStateEqual(driver.providerProperties, config.providerProperties, data.info.providerProperties)) + !isObjectPropertyInfoStateEqual(driver.providerProperties, config.providerProperties, data.info.providerProperties)) || + (config.keepAliveInterval !== undefined && !isValuesEqual(config.keepAliveInterval, data.info.keepAliveInterval)) ) { stateContext.markEdited(); } diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ProviderPropertiesForm.tsx b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ProviderPropertiesForm.tsx index 9bf444d79c..24125b1345 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ProviderPropertiesForm.tsx +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ProviderPropertiesForm.tsx @@ -13,7 +13,9 @@ import { getPropertyControlType, Group, GroupTitle, + InputField, ObjectPropertyInfoForm, + useCustomInputValidation, useObjectPropertyCategories, useTranslate, } from '@cloudbeaver/core-blocks'; @@ -28,9 +30,10 @@ interface Props { readonly?: boolean; } +const MAX_KEEP_ALIVE_INTERVAL_IN_SECONDS = 32767; + export const ProviderPropertiesForm = observer(function ProviderPropertiesForm({ config, properties, disabled, readonly }) { const translate = useTranslate(); - const supportedProperties = properties.filter(property => property.supportedConfigurationTypes?.some(type => type === config.configurationType)); const { categories, isUncategorizedExists } = useObjectPropertyCategories(supportedProperties); @@ -74,6 +77,21 @@ export const ProviderPropertiesForm = observer(function ProviderPropertie )} + + {translate('connections_connection_keep_alive')} + + {categories.map((category, index) => (