Merge branch 'devel' into dbeaver/pro#9335-Not-keep-metadata-queries-in-QMDB

This commit is contained in:
Viktor Kirst
2026-08-12 15:35:25 +04:00
committed by GitHub
94 changed files with 891 additions and 230 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.13.2</version>
<version>2.14.0</version>
</dependency>
</dependencies>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model.cli;singleton:=true
Bundle-Version: 1.0.16.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.17.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.cli;visibility:=reexport
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model.cli</artifactId>
<version>1.0.16-SNAPSHOT</version>
<version>1.0.17-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Model
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
Bundle-Version: 1.0.107.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.108.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.107-SNAPSHOT</version>
<version>1.0.108-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -235,6 +235,14 @@ public class ServletAppUtils {
response.addCookie(sessionCookie);
}
public static String getHeaderOrLegacy(HttpServletRequest request, String headerName, String legacyHeaderName) {
String value = request.getHeader(headerName);
if (CommonUtils.isEmpty(value)) {
value = request.getHeader(legacyHeaderName);
}
return value;
}
public static String getRequestCookie(HttpServletRequest request, String cookieName) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Community Product
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.2.0.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.153.qualifier
Bundle-Release-Date: 20260831
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.152-SNAPSHOT</version>
<version>1.0.153-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver Community
Bundle-SymbolicName: io.cloudbeaver.server.ce;singleton:=true
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.2.0.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.ce</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Server
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
Bundle-Version: 26.1.5.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 26.2.0.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -215,6 +215,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
ps.handleWebSessionEvent(new WSSocketConnectedEvent(ws.getApplication().getApplicationRunId()));
resp.setHeader(WSConstants.WS_SESSION_HEADER, sid);
resp.setHeader(WSConstants.WS_SESSION_HEADER_LEGACY, sid);
log.debug("HTTP Long-Poll channel opened for session " + sid);
return ps;
@@ -304,7 +305,7 @@ public class CBEventsLongPollingServlet extends HttpServlet {
@Override
public BaseWebSession resolve(@NotNull HttpServletRequest req) throws SMException {
String sid = req.getHeader(WSConstants.WS_SESSION_HEADER);
String sid = ServletAppUtils.getHeaderOrLegacy(req, WSConstants.WS_SESSION_HEADER, WSConstants.WS_SESSION_HEADER_LEGACY);
String token = req.getHeader(WSConstants.WS_AUTH_HEADER);
if (CommonUtils.isEmpty(sid) && CommonUtils.isEmpty(token)) {
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Administration
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.151-SNAPSHOT</version>
<version>1.0.152-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver AI Integration
Bundle-SymbolicName: io.cloudbeaver.service.ai;singleton:=true
Bundle-Version: 1.0.1.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.2.qualifier
Bundle-Release-Date: 20260831
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.service.ai
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ai</artifactId>
<version>1.0.1-SNAPSHOT</version>
<version>1.0.2-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -274,6 +274,9 @@ extend type Mutation @since(version: "23.2.2") {
"Clears the chat messages in the AI chat conversation. The messages will be removed from the conversation."
aiClearLastChatMessages(conversationId: ID!, messageId: ID!): Boolean! @since(version: "25.1.1")
"Cancels the in-progress AI response generation in the specified conversation."
aiCancelChatMessage(conversationId: ID!): Boolean! @since(version: "26.1.5")
"Saves AI settings (e.g. supporting confirming metadata transfer) for the specified connection."
aiSaveDataSourceSettings(dataSourceId: DataSourceIdInput!, settings: AIDataSourceSettingsInput!): AIDataSourceSettingsInfo! @since(version: "25.3.3")
}
@@ -24,6 +24,8 @@ import io.cloudbeaver.service.ai.model.WebAISendChatMessageInfo;
import io.cloudbeaver.service.ai.model.WebAiChatResponseConsumer;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
import io.cloudbeaver.utils.ServletAppUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
@@ -39,9 +41,9 @@ import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.navigator.DBNDatabaseNode;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNUtils;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.utils.RuntimeUtils;
import org.jkiss.utils.CommonUtils;
import java.time.Clock;
@@ -139,10 +141,11 @@ public class WebAIUtils {
@Nullable AIConfirmation confirmation,
@NotNull String jobName
) {
webSession.setAttribute(getWaitingAttr(conversation), true);
CompletableFuture<AIChatConversation> result = new CompletableFuture<>();
RuntimeUtils.scheduleJob(
jobName, monitor -> {
AbstractJob job = new AbstractJob(jobName) {
@NotNull
@Override
protected IStatus run(@NotNull DBRProgressMonitor monitor) {
try {
AIChatResponseConsumer subscriber = new WebAiChatResponseConsumer(conversation, webSession, aiChatSession);
aiChatSession.processAICompletion(
@@ -159,15 +162,25 @@ public class WebAIUtils {
}
});
} catch (DBException e) {
log.error("Error processing AI completion", e);
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, errorMessage);
if (monitor.isCanceled()) {
log.debug("AI completion cancelled", e);
} else {
log.error("Error processing AI completion", e);
var errorMessage = conversation.addMessage(AIMessage.errorMessage(e));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, errorMessage);
}
} finally {
webSession.removeAttribute(getWaitingAttr(conversation));
// Only clear the flag if it still points to this job
if (webSession.getAttribute(getWaitingAttr(conversation)) == this) {
webSession.removeAttribute(getWaitingAttr(conversation));
}
}
return Status.OK_STATUS;
}
);
};
webSession.setAttribute(getWaitingAttr(conversation), job);
job.schedule();
return result;
}
@@ -257,21 +270,25 @@ public class WebAIUtils {
throw new DBWebException("AI services restricted for '%s'. Please contact your administrator if you need it.".formatted(
conversation.getDataSource()));
}
String caption = conversation.getCaption();
AIChatMessage promptMessage = conversation.addMessage(message);
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, promptMessage);
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
AIChatMessage promptMessage;
AIChatMessage result;
synchronized (conversation) {
String caption = conversation.getCaption();
promptMessage = conversation.addMessage(message);
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(promptMessage, conversation)));
aiChatSession.notifyMessageAdd(conversation, promptMessage);
if (!CommonUtils.equalObjects(caption, conversation.getCaption())) {
aiChatSession.notifyConversationRenamed(conversation, conversation.getCaption());
}
if (!AIUtils.hasValidConfiguration()) {
throw new DBWebException("Invalid AI configuration");
}
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
throw new DBWebException("Conversation is already waiting for response");
}
result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
}
if (!AIUtils.hasValidConfiguration()) {
throw new DBWebException("Invalid AI configuration");
}
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) != null) {
throw new DBWebException("Conversation is already waiting for response");
}
AIChatMessage result = new AIChatMessage(conversation.getNextMessageId(), AIMessage.assistantMessage("", null));
WebAIUtils.scheduleConversationSubmission(webSession, aiChatSession, conversation, null, "AI completion");
return new WebAISendChatMessageInfo(
new WebAIChatConversation(webSession, conversation),
new WebAIMessage(promptMessage, conversation),
@@ -139,6 +139,12 @@ public interface DBWServiceAI extends DBWService {
@NotNull String messageId
) throws DBWebException;
@WebAction
boolean cancelChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId
) throws DBWebException;
@NotNull
@WebAction
WebAIDataSourceSettings getDataSourceAiSettings(
@@ -25,6 +25,7 @@ import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.service.ai.WebAIUtils;
import io.cloudbeaver.service.ai.model.*;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
import io.cloudbeaver.service.ai.model.inputs.DataSourceId;
import io.cloudbeaver.service.ai.model.inputs.WebAIChatConversationInput;
import io.cloudbeaver.service.ai.model.inputs.WebAIConfigurationProfileInput;
@@ -46,12 +47,14 @@ import org.jkiss.dbeaver.model.ai.engine.AIDatabaseContext;
import org.jkiss.dbeaver.model.ai.engine.AIEngine;
import org.jkiss.dbeaver.model.ai.engine.AIEngineProperties;
import org.jkiss.dbeaver.model.ai.engine.AIModel;
import org.jkiss.dbeaver.model.ai.internal.AIChatMessages;
import org.jkiss.dbeaver.model.ai.prompt.AIPromptGenerateSql;
import org.jkiss.dbeaver.model.ai.registry.*;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.logical.DBSLogicalDataSource;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.websocket.event.WSWorkspaceConfigurationChangedEvent;
import org.jkiss.dbeaver.runtime.DBWorkbench;
@@ -397,6 +400,33 @@ public class WebServiceAI implements DBWServiceAI {
return true;
}
@Override
public boolean cancelChatMessage(
@NotNull WebSession webSession,
@NotNull String conversationId
) throws DBWebException {
WebAIUtils.validateAiPluginEnabled();
AIChatConversation conversation = WebAIUtils.getAiChatConversation(webSession, conversationId);
synchronized (conversation) {
boolean completionStarted = conversation.isActive();
conversation.cancelConversation();
boolean hadPendingJob = false;
if (webSession.getAttribute(WebAIUtils.getWaitingAttr(conversation)) instanceof AbstractJob job) {
hadPendingJob = true;
job.cancel();
}
webSession.removeAttribute(WebAIUtils.getWaitingAttr(conversation));
if (hadPendingJob && !completionStarted) {
// The completion job was still queued, so its response consumer will not be called.
// We need to add a cancellation message to the conversation and notify the client.
AIChatMessage cancelMessage = conversation.addMessage(
AIMessage.warningMessage(AIChatMessages.ai_chat_conversation_cancelled));
webSession.addSessionEvent(new WSAiChatMessageEvent(new WebAIMessage(cancelMessage, conversation)));
}
}
return true;
}
@NotNull
@Override
public WebAIDataSourceSettings getDataSourceAiSettings(
@@ -163,6 +163,12 @@ public class WebServiceBindingAI extends WebServiceBindingBase<DBWServiceAI> imp
getArgumentVal(env, "conversationId"),
getArgumentVal(env, "messageId")
)
).dataFetcher(
"aiCancelChatMessage",
env -> getService(env).cancelChatMessage(
getWebSession(env),
getArgumentVal(env, "conversationId")
)
).dataFetcher(
"aiCreateProfile", env -> getService(env).createProfile(
getWebSession(env),
@@ -1,18 +1,18 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* All Rights Reserved.
* 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
*
* NOTICE: All information contained herein is, and remains
* the property of DBeaver Corp and its suppliers, if any.
* The intellectual and technical concepts contained
* herein are proprietary to DBeaver Corp and its suppliers
* and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from DBeaver Corp.
* 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.service.ai.model;
@@ -22,9 +22,11 @@ import io.cloudbeaver.service.ai.model.events.WSAiChatMessageErrorEvent;
import io.cloudbeaver.service.ai.model.events.WSAiChatMessageEvent;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.dbeaver.model.ai.internal.AIChatMessages;
import org.jkiss.utils.CommonUtils;
import java.util.List;
import java.util.concurrent.CancellationException;
public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
private final StringBuilder responseBuilder;
@@ -78,7 +80,12 @@ public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
@Override
public void error(@NotNull Throwable throwable) {
var errorMessage = conversation.addMessage(AIMessage.errorMessage(throwable));
AIMessage aiMessage = throwable instanceof CancellationException cancellationException
? AIMessage.warningMessage(cancellationException.getMessage())
: AIMessage.errorMessage(throwable);
AIChatMessage errorMessage = conversation.addMessage(aiMessage);
if (responseBuilder.isEmpty()) {
webSession.addSessionEvent(
new WSAiChatMessageEvent(new WebAIMessage(errorMessage, conversation)));
@@ -94,12 +101,19 @@ public class WebAiChatResponseConsumer implements AIChatResponseConsumer {
}
@Override
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation) {
public void complete(@NotNull List<AIMessageMeta> meta, boolean finishConversation, boolean isCanceled) {
if (responseBuilder.isEmpty()) {
if (isCanceled) {
warning(AIChatMessages.ai_chat_conversation_cancelled);
}
return;
}
AIChatMessage responseMessage = conversation.addMessage(AIMessage.assistantMessage(responseBuilder.toString(), meta));
chatSession.notifyMessageAdd(conversation, responseMessage);
webSession.addSessionEvent(new WSAiChatMessageChunkEvent(conversation.getId(), responseMessage.id(), null, true));
if (isCanceled) {
warning(AIChatMessages.ai_chat_conversation_cancelled);
}
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Authentication
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
Bundle-Version: 1.0.151.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.151-SNAPSHOT</version>
<version>1.0.152-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Data Transfer
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
Bundle-Version: 1.0.152.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.153.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.152-SNAPSHOT</version>
<version>1.0.153-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -48,6 +48,8 @@ input DataTransferImportSettings {
useBulkLoad: Boolean
"Use transactions during import"
useTransactions: Boolean
"Open new connection(s) for the import task"
openNewConnection: Boolean
}
input DataTransferImportParameters {
@@ -425,7 +425,7 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
throw new DBWebException("Import is canceled");
}
} catch (DBException e) {
throw new DBWebException("Import failed cause: " + e.getMessage());
throw new DBWebException("Import failed", e);
}
}
}
@@ -446,6 +446,9 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
consumerSettings.setUseTransactions(CommonUtils.getBoolean(
settings.get(DTConstants.PROP_USE_TRANSACTIONS),
consumerSettings.isUseTransactions()));
consumerSettings.setOpenNewConnections(CommonUtils.getBoolean(
settings.get(DTConstants.PROP_OPEN_NEW_CONNECTION),
consumerSettings.isOpenNewConnections()));
}
}
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - File System
Bundle-SymbolicName: io.cloudbeaver.service.fs;singleton:=true
Bundle-Version: 1.0.69.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.70.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server.ce,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.69-SNAPSHOT</version>
<version>1.0.70-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - LDAP
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.ldap.auth;singleton:=true
Bundle-Version: 1.0.34.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.35.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.ldap.auth</artifactId>
<version>1.0.34-SNAPSHOT</version>
<version>1.0.35-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.155.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.156.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.155-SNAPSHOT</version>
<version>1.0.156-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Resource manager NIO implementation
Bundle-SymbolicName: io.cloudbeaver.service.rm.nio;singleton:=true
Bundle-Version: 1.0.69.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.70.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.model,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.69-SNAPSHOT</version>
<version>1.0.70-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Resource manager
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
Bundle-Version: 1.0.104.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.105.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: io.cloudbeaver.server,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.104-SNAPSHOT</version>
<version>1.0.105-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Name: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.107.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.108.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport,
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.107-SNAPSHOT</version>
<version>1.0.108-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: CloudBeaver SLF4j Binding
Bundle-SymbolicName: io.cloudbeaver.slf4j;singleton:=true
Bundle-Version: 1.0.67.qualifier
Bundle-Release-Date: 20260817
Bundle-Version: 1.0.68.qualifier
Bundle-Release-Date: 20260831
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Fragment-Host: slf4j.api
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.67-SNAPSHOT</version>
<version>1.0.68-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.175.qualifier"
version="1.0.176.qualifier"
provider-name="DBeaver Corp">
<plugin id="io.cloudbeaver.resources.drivers.base" version="0.0.0"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
<version>1.0.175-SNAPSHOT</version>
<version>1.0.176-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="26.1.5.qualifier"
version="26.2.0.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="26.1.5.qualifier"
version="26.2.0.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.105.qualifier"
version="1.0.106.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.105-SNAPSHOT</version>
<version>1.0.106-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<?pde version="3.5"?>
<product name="CloudBeaver Community" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
application="io.cloudbeaver.product.ce.application" version="26.1.5.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="26.2.0.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>26.1.5-SNAPSHOT</version>
<version>26.2.0-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
+3 -1
View File
@@ -22,7 +22,9 @@
"js-yaml": ">=5.2.2",
"brace-expansion": ">=5.0.9",
"axios": ">=1.18.0",
"postcss": ">=8.5.18"
"postcss": ">=8.5.18",
"nanoid": ">=3.3.17",
"less": ">=4.7.0"
},
"scripts": {
"test": "dbeaver-test",
@@ -17,7 +17,8 @@ import {
getObjectPropertyValueType,
type IObjectPropertyInfo,
} from '@cloudbeaver/core-sdk';
import { EMPTY_ARRAY, removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
import { EMPTY_ARRAY, getTextFileReadingProcess, removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
import { clsx } from '@dbeaver/ui-kit';
import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox.js';
import { Select } from '../../FormControls/Select.js';
@@ -30,6 +31,8 @@ import { Link } from '../../Link.js';
import { useTranslate } from '../../localization/useTranslate.js';
import { evaluate } from '../evaluate.js';
import { SAVED_VALUE_INDICATOR } from '../../SAVED_VALUE_INDICATOR.js';
import { UploadArea } from '../../UploadArea.js';
import { Button } from '../../Button.js';
interface RenderFieldProps {
property: IObjectPropertyInfo;
@@ -204,6 +207,46 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
);
}
if (controlType === 'uploadable-textarea' && state && property.id) {
async function handleFileUpload(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) {
throw new Error('File is not found');
}
const process = getTextFileReadingProcess(file);
const result = await process.promise;
if (state && property.id) {
state[property.id] = result;
}
}
return (
<div className="tw:flex tw:flex-col tw:gap-2">
<Textarea
required={required}
title={state[property.id]}
labelTooltip={property.description || property.displayName}
placeholder={placeholder}
name={property.id}
state={state}
disabled={disabled}
readOnly={readonly}
className={clsx('tw:flex-0!', className)}
>
{property.displayName ?? ''}
</Textarea>
<UploadArea disabled={disabled || readonly} reset onChange={handleFileUpload}>
<Button className="tw:w-max" tag="div" disabled={disabled || readonly} variant="secondary">
{translate('ui_file')}
</Button>
</UploadArea>
</div>
);
}
if (controlType === 'file' && state) {
return (
<InputFileTextContent
@@ -7,10 +7,12 @@
*/
import type { IObjectPropertyInfo } from './IObjectPropertyInfo.js';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'textarea' | 'file' | 'input';
export type ObjectPropertyType = 'checkbox' | 'selector' | 'link' | 'textarea' | 'file' | 'input' | 'uploadable-textarea';
export function getObjectPropertyType(property: IObjectPropertyInfo): ObjectPropertyType {
const dataType = property.dataType?.toLowerCase();
const isTextarea = dataType === 'string' && property.length === 'MULTILINE';
const isFile = property.features.includes('file');
if (dataType === 'boolean') {
return 'checkbox';
@@ -18,9 +20,11 @@ export function getObjectPropertyType(property: IObjectPropertyInfo): ObjectProp
return 'selector';
} else if (property.features.includes('href')) {
return 'link';
} else if (dataType === 'string' && property.length === 'MULTILINE') {
} else if (isTextarea && isFile) {
return 'uploadable-textarea';
} else if (isTextarea) {
return 'textarea';
} else if (property.features.includes('file')) {
} else if (isFile) {
return 'file';
}
@@ -0,0 +1,3 @@
mutation cancelConversation($conversationId: ID!) {
result: aiCancelChatMessage(conversationId: $conversationId)
}
@@ -0,0 +1,9 @@
query getConnectionImportDriverConfiguration($projectId: ID, $connectionId: ID!) {
connections: userConnections(projectId: $projectId, id: $connectionId) {
id
projectId
driverConfiguration {
...DriverConfiguration
}
}
}
@@ -0,0 +1,9 @@
fragment DriverConfiguration on DriverConfiguration {
supportedInsertReplaceMethods {
id
name
description
}
supportsBulkLoad
supportsTransactions
}
@@ -92,6 +92,11 @@ export class AIChatConversationsResource extends CachedMapResource<string, AICha
return this.get(conversation.id)!;
}
async cancelConversation(conversationId: string): Promise<boolean> {
const { result } = await this.graphQLService.sdk.cancelConversation({ conversationId });
return result;
}
protected async loader(originalKey: ResourceKey<string>): Promise<Map<string, AIChatConversationInfo>> {
const conversationList: AIChatConversationInfo[] = [];
@@ -13,10 +13,12 @@ import { ActionIconButton, AutoResizeTextarea, Form, s, useS, useTranslate } fro
import { useService } from '@cloudbeaver/core-di';
import { getOS, OperatingSystem } from '@cloudbeaver/core-utils';
import { NotificationService } from '@cloudbeaver/core-events';
import { Command } from '@dbeaver/ui-kit';
import { AIChatMessageService } from './AIChatMessageService.js';
import { AIChatConversationsService } from '../AIChatConversation/AIChatConversationsService.js';
import { AIChatContext } from '../AIChatContext.js';
import { AIChatConversationsResource } from '../AIChatConversation/AIChatConversationsResource.js';
import classes from './AIChatMessageForm.module.css';
interface Props {
@@ -30,6 +32,7 @@ export const AIChatMessageForm = observer<PropsWithChildren<Props>>(function AIC
const notificationService = useService(NotificationService);
const aiChatMessageService = useService(AIChatMessageService);
const aiChatConversationsService = useService(AIChatConversationsService);
const aiChatConversationsResource = useService(AIChatConversationsResource);
const [value, setValue] = useState('');
@@ -54,6 +57,16 @@ export const AIChatMessageForm = observer<PropsWithChildren<Props>>(function AIC
}
}
async function cancel() {
if (currentConversationId) {
try {
await aiChatConversationsResource.cancelConversation(currentConversationId);
} catch (exception: any) {
notificationService.logException(exception, 'plugin_ai_chat_conversation_cancel_failed');
}
}
}
function getPlaceholder() {
const OS = getOS();
const symbol = OS === OperatingSystem.macOS ? '⌘' : 'Ctrl';
@@ -65,7 +78,12 @@ export const AIChatMessageForm = observer<PropsWithChildren<Props>>(function AIC
return (
<div className={s(styles, { container: true })}>
<Form className="tw:flex tw:items-end tw:gap-2" disableEnterSubmit={disabled} contents onSubmit={sendMessage}>
<Form
className="tw:flex tw:items-end tw:gap-2"
disableEnterSubmit={disabled}
contents
onSubmit={aiChatConversationsService.processing ? cancel : sendMessage}
>
<div className={s(styles, { textareaContainer: true })}>
<AutoResizeTextarea
className={s(styles, { textarea: true })}
@@ -74,7 +92,16 @@ export const AIChatMessageForm = observer<PropsWithChildren<Props>>(function AIC
autoFocus
onChange={v => setValue(v)}
/>
<ActionIconButton name="/icons/send.svg" disabled={disabled} img onClick={sendMessage} />
{!aiChatConversationsService.processing ? (
<ActionIconButton name="/icons/send.svg" disabled={disabled} img onClick={sendMessage} />
) : (
<Command
className="tw:cursor-pointer tw:w-10 tw:h-10 tw:bg-[var(--theme-primary)] tw:rounded-md tw:flex tw:items-center tw:justify-center tw:focus:opacity-80 tw:hover:opacity-80 tw:transition-opacity"
onClick={cancel}
>
<div className="tw:w-3 tw:h-3 tw:bg-[var(--theme-surface)] tw:rounded-xs" />
</Command>
)}
</div>
</Form>
{children}
@@ -44,6 +44,8 @@ export default [
['plugin_ai_chat_conversation_history_date_group_last_7_days', 'Last 7 days'],
['plugin_ai_chat_conversation_history_date_group_over_week_ago', 'Over a week ago'],
['plugin_ai_chat_conversation_cancel_failed', 'Failed to cancel the conversation'],
['plugin_ai_chat_scope_change', 'Configure AI context'],
['plugin_ai_chat_scope_change_fail', 'Failed to change context'],
['plugin_ai_chat_profile_group', 'Active configuration'],
@@ -44,6 +44,8 @@ export default [
['plugin_ai_chat_conversation_history_date_group_last_7_days', 'Last 7 days'],
['plugin_ai_chat_conversation_history_date_group_over_week_ago', 'Over a week ago'],
['plugin_ai_chat_conversation_cancel_failed', 'Impossibile annullare la conversazione'],
['plugin_ai_chat_scope_change', 'Configure AI context'],
['plugin_ai_chat_scope_change_fail', 'Failed to change context'],
['plugin_ai_chat_profile_group', 'Active configuration'],
@@ -44,6 +44,8 @@ export default [
['plugin_ai_chat_conversation_history_date_group_last_7_days', 'Последние 7 дней'],
['plugin_ai_chat_conversation_history_date_group_over_week_ago', 'Более недели назад'],
['plugin_ai_chat_conversation_cancel_failed', 'Не удалось отменить разговор'],
['plugin_ai_chat_scope_change', 'Настроить AI контекст'],
['plugin_ai_chat_scope_change_fail', 'Не удалось изменить контекст'],
['plugin_ai_chat_profile_group', 'Активная конфигурация'],
@@ -44,6 +44,8 @@ export default [
['plugin_ai_chat_conversation_history_date_group_last_7_days', 'Last 7 days'],
['plugin_ai_chat_conversation_history_date_group_over_week_ago', 'Over a week ago'],
['plugin_ai_chat_conversation_cancel_failed', '无法取消对话'],
['plugin_ai_chat_scope_change', 'Configure AI context'],
['plugin_ai_chat_scope_change_fail', 'Failed to change context'],
['plugin_ai_chat_profile_group', 'Active configuration'],
@@ -5,13 +5,16 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import { AUTH_PROVIDER_LOCAL_ID, AuthProvidersResource, isLocalUser, UsersResource } from '@cloudbeaver/core-authentication';
import {
Container,
GroupTitle,
InputField,
useExecutor,
useFormCustomInputValidation,
usePasswordValidation,
useResource,
@@ -39,6 +42,8 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
const authProvidersResource = useResource(UserFormInfoCredentials, AuthProvidersResource, null);
const passwordValidationRef = usePasswordValidation();
const [repeatedPassword, setRepeatedPassword] = useState('');
let local = authProvidersResource.resource.isEnabled(AUTH_PROVIDER_LOCAL_ID);
if (!local) {
@@ -66,6 +71,15 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
return null;
});
useExecutor({
executor: formState.submitTask,
handlers: [
function handleSubmit(data) {
setRepeatedPassword('');
},
],
});
return (
<Container gap vertical>
<GroupTitle keepSize>{translate('authentication_user_credentials')}</GroupTitle>
@@ -102,13 +116,14 @@ export const UserFormInfoCredentials = observer<Props>(function UserFormInfoCred
<InputField
ref={passwordRepeatRef}
type="password"
name="passwordRepeat"
placeholder={editing ? PASSWORD_PLACEHOLDER : ''}
readOnly={disabled}
required={!editing}
canShowPassword
value={repeatedPassword}
canShowPassword={repeatedPassword !== ''}
keepSize
tiny
onChange={v => setRepeatedPassword(v)}
>
{translate('authentication_user_password_repeat')}
</InputField>
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dial
import { CreateTeamService } from './Teams/TeamsTable/CreateTeamService.js';
import { EUsersAdministrationSub, UsersAdministrationNavigationService } from './UsersAdministrationNavigationService.js';
import { CreateUserService } from './UsersTable/CreateUserService.js';
import type { IUserFilters } from './UsersTable/Filters/useUsersTableFilters.js';
const UserCredentialsList = React.lazy(async () => {
const { UserCredentialsList } = await import('./UsersTable/UserCredentialsList.js');
@@ -33,15 +34,25 @@ const UsersAdministration = React.lazy(async () => {
return { default: UsersAdministration };
});
const UsersTableFilterButton = React.lazy(async () => {
const { UsersTableFilterButton } = await import('./UsersTable/Filters/UsersTableFilterButton.js');
return { default: UsersTableFilterButton };
});
export interface IUserDetailsInfoProps {
user: AdminUser;
}
export interface IUsersActionButtonProps {
filters: IUserFilters;
}
@injectable(() => [AdministrationItemService, CreateUserService, TeamsResource, CreateTeamService, UsersResource, CommonDialogService])
export class UsersAdministrationService extends Bootstrap {
readonly tabsContainer: TabsContainer;
readonly userDetailsInfoPlaceholder: PlaceholderContainer<IUserDetailsInfoProps>;
readonly informationPlaceholder: PlaceholderContainer;
readonly actionButtonsPlaceholder: PlaceholderContainer<IUsersActionButtonProps>;
administrationItem!: IAdministrationItem;
constructor(
@@ -56,6 +67,7 @@ export class UsersAdministrationService extends Bootstrap {
this.userDetailsInfoPlaceholder = new PlaceholderContainer();
this.tabsContainer = new TabsContainer('Access Control');
this.informationPlaceholder = new PlaceholderContainer();
this.actionButtonsPlaceholder = new PlaceholderContainer();
}
override register(): void {
@@ -83,6 +95,7 @@ export class UsersAdministrationService extends Bootstrap {
getDrawerComponent: () => UsersDrawerItem,
});
this.userDetailsInfoPlaceholder.add(UserCredentialsList, 0);
this.actionButtonsPlaceholder.add(UsersTableFilterButton, 0);
}
private async handleDeactivate(sub: EUsersAdministrationSub) {
@@ -0,0 +1,29 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { observer } from 'mobx-react-lite';
import { clsx, IconButton, type IconButtonProps } from '@dbeaver/ui-kit';
const BUTTON_BOX_CLASS_NAME = clsx(
'theme-form-element-radius theme-background-surface theme-text-on-surface theme theme-border-color-background',
'tw:flex tw:items-center tw:justify-center tw:box-border tw:h-8 tw:border-2',
);
const BUTTON_CLASS_NAME = 'tw:relative! tw:box-border! tw:bg-inherit! tw:cursor-pointer! tw:w-[28px]! tw:h-full! tw:p-1! tw:hover:opacity-80!';
export interface IFiltersActionButtonProps extends IconButtonProps {
active?: boolean;
}
export const FiltersActionButton = observer<IFiltersActionButtonProps>(function FiltersActionButton({ active, className, size = 'small', ...rest }) {
return (
<div className={BUTTON_BOX_CLASS_NAME}>
<IconButton className={clsx(BUTTON_CLASS_NAME, active && 'tw:bg-(--theme-secondary)!', className)} size={size} {...rest} />
</div>
);
});
@@ -0,0 +1,32 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { IconOrImage, useTranslate } from '@cloudbeaver/core-blocks';
import type { IUsersActionButtonProps } from '../../UsersAdministrationService.js';
import { FiltersActionButton } from './FiltersActionButton.js';
import { UsersTableFiltersOpenContext } from './UsersTableFiltersOpenContext.js';
export const UsersTableFilterButton = observer<IUsersActionButtonProps>(function UsersTableFilterButton() {
const translate = useTranslate();
const openContext = useContext(UsersTableFiltersOpenContext);
if (!openContext) {
throw new Error('UsersTableFilterButton must be rendered within UsersTableFiltersOpenContext');
}
const label = translate('authentication_administration_users_filters_filter_label');
return (
<FiltersActionButton active={openContext.open} aria-label={label} title={label} onClick={openContext.onToggle}>
<IconOrImage className="tw:w-full tw:h-full" icon="filter" />
</FiltersActionButton>
);
});
@@ -1,11 +1,3 @@
/*
* 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.
*/
.filterContainer {
display: flex;
gap: 12px;
@@ -16,31 +8,7 @@
}
.actions {
composes: theme-form-element-radius theme-background-surface theme-text-on-surface theme theme-border-color-background from global;
box-sizing: border-box;
border: 2px solid;
height: 32px;
}
.button {
position: relative;
box-sizing: border-box;
background: inherit;
cursor: pointer;
width: 28px;
height: 100%;
padding: 4px;
&:hover {
opacity: 0.8;
}
}
.buttonActive {
background: var(--theme-secondary);
}
.iconOrImage {
width: 100%;
height: 100%;
display: flex;
align-items: center;
gap: 8px;
}
@@ -6,14 +6,16 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { Filter, Group, IconOrImage, Loader, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { Filter, Group, Loader, Placeholder, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { UsersAdministrationService } from '../../UsersAdministrationService.js';
import styles from './UsersTableFilters.module.css';
import { UsersTableFiltersDetails } from './UsersTableFiltersDetails.js';
import { UsersTableFiltersOpenContext } from './UsersTableFiltersOpenContext.js';
import { type IUserFilters } from './useUsersTableFilters.js';
import { IconButton } from '@dbeaver/ui-kit';
interface Props {
filters: IUserFilters;
@@ -22,8 +24,11 @@ interface Props {
export const UsersTableFilters = observer<Props>(function UsersTableFilters({ filters }) {
const translate = useTranslate();
const style = useS(styles);
const usersAdministrationService = useService(UsersAdministrationService);
const [open, setOpen] = useState(false);
const onToggle = useCallback(() => setOpen(open => !open), []);
const openContext = useMemo(() => ({ open, onToggle }), [open, onToggle]);
return (
<Group parent compact gap>
@@ -35,14 +40,9 @@ export const UsersTableFilters = observer<Props>(function UsersTableFilters({ fi
onChange={filters.setSearch}
/>
<div className={s(style, { actions: true })}>
<IconButton
className={s(style, { button: true, buttonActive: open })}
size="small"
aria-label={translate('authentication_administration_users_filters_filter_label')}
onClick={() => setOpen(!open)}
>
<IconOrImage className={s(style, { iconOrImage: true })} icon="filter" />
</IconButton>
<UsersTableFiltersOpenContext.Provider value={openContext}>
<Placeholder container={usersAdministrationService.actionButtonsPlaceholder} filters={filters} />
</UsersTableFiltersOpenContext.Provider>
</div>
</div>
@@ -0,0 +1,15 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { createContext } from 'react';
export interface IUsersTableFiltersOpenContext {
open: boolean;
onToggle: () => void;
}
export const UsersTableFiltersOpenContext = createContext<IUsersTableFiltersOpenContext | null>(null);
@@ -22,3 +22,5 @@ export * from './AdministrationUsersManagementService.js';
export * from './externalUserProviderStatusContext.js';
export * from './Administration/Users/Teams/TeamsForm/Options/getTeamOptionsFormPart.js';
export * from './Administration/Users/UsersTable/UsersTableOptionsPanelService.js';
export * from './Administration/Users/UsersTable/Filters/useUsersTableFilters.js';
export * from './Administration/Users/UsersTable/Filters/FiltersActionButton.js';
@@ -23,6 +23,7 @@
},
"dependencies": {
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-dialogs": "workspace:*",
"@cloudbeaver/core-events": "workspace:*",
@@ -5,6 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createConnectionParam } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { ACTION_IMPORT, ActionService, menuExtractItems, MenuService } from '@cloudbeaver/core-view';
@@ -72,16 +73,20 @@ export class DataImportBootstrap extends Bootstrap {
throw new Error('Execution context must be provided');
}
const { status, result: dialogResult } = await this.commonDialogService.open(DataImportDialogLazy, { tableName: model.name ?? model.id });
const connectionKey = createConnectionParam(executionContext.projectId, executionContext.connectionId);
const { status, result: dialogResult } = await this.commonDialogService.open(DataImportDialogLazy, {
tableName: model.name ?? model.id,
connectionKey,
});
if (status === DialogueStateResult.Resolved && dialogResult) {
const success = await this.dataImportService.importData(
executionContext.connectionId,
connectionKey,
executionContext.id,
executionContext.projectId,
result.id,
dialogResult.processorId,
dialogResult.file,
dialogResult.settings,
);
if (success) {
@@ -1,28 +1,42 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 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 { observer } from 'mobx-react-lite';
import { Button, CommonDialogBody, CommonDialogFooter, CommonDialogHeader, CommonDialogWrapper, useTranslate } from '@cloudbeaver/core-blocks';
import {
Button,
CommonDialogBody,
CommonDialogFooter,
CommonDialogHeader,
CommonDialogWrapper,
useResource,
useTranslate,
} from '@cloudbeaver/core-blocks';
import type { IConnectionInfoParams } from '@cloudbeaver/core-connections';
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
import type { DataTransferImportSettings } from '@cloudbeaver/core-sdk';
import { DataImportDriverConfigurationResource } from '../DataImportDriverConfigurationResource.js';
import { DataImportFileSelector } from './DataImportFileSelector.js';
import { EDataImportDialogStep } from './EDataImportDialogStep.js';
import type { IDataImportDialogState } from './IDataImportDialogState.js';
import { ImportProcessorList } from './ImportProcessorList.js';
import { ImportSettingsForm } from './ImportSettingsForm.js';
import { useDataImportDialog } from './useDataImportDialog.js';
export interface IDataImportDialogResult {
file: File;
processorId: string;
settings: DataTransferImportSettings;
}
export interface IDataImportDialogPayload {
tableName: string;
connectionKey: IConnectionInfoParams;
initialState?: IDataImportDialogState;
}
@@ -33,38 +47,52 @@ export const DataImportDialog: DialogComponent<IDataImportDialogPayload, IDataIm
}) {
const translate = useTranslate();
const dialog = useDataImportDialog(payload.initialState);
const driverConfigurationResource = useResource(DataImportDialog, DataImportDriverConfigurationResource, payload.connectionKey, { silent: true });
const driverConfiguration = driverConfigurationResource.tryGetData ?? null;
const isSettingsStep = dialog.state.step === EDataImportDialogStep.Settings;
let title = translate('plugin_data_import_title');
let icon = '/icons/data-import.svg';
if (dialog.state.step === EDataImportDialogStep.File && dialog.state.selectedProcessor) {
if (dialog.state.step !== EDataImportDialogStep.Processor && dialog.state.selectedProcessor) {
title += ` (${dialog.state.selectedProcessor.name ?? dialog.state.selectedProcessor.id})`;
icon = dialog.state.selectedProcessor.icon ?? icon;
}
function importData() {
if (dialog.state.file && dialog.state.selectedProcessor) {
resolveDialog({ file: dialog.state.file, processorId: dialog.state.selectedProcessor.id, settings: dialog.state.settings });
}
}
return (
<CommonDialogWrapper size="large" fixedSize>
<CommonDialogHeader title={title} subTitle={payload.tableName} icon={icon} onReject={rejectDialog} />
<CommonDialogBody noBodyPadding>
{dialog.state.step === EDataImportDialogStep.Processor && <ImportProcessorList onSelect={dialog.selectProcessor} />}
{dialog.state.step === EDataImportDialogStep.File && <DataImportFileSelector state={dialog.state} onDelete={dialog.deleteFile} />}
{dialog.state.step === EDataImportDialogStep.Settings && driverConfiguration && (
<ImportSettingsForm settings={dialog.state.settings} driverConfiguration={driverConfiguration} />
)}
</CommonDialogBody>
<CommonDialogFooter>
<Button type="button" variant="secondary" onClick={() => rejectDialog()}>
{translate('ui_processing_cancel')}
</Button>
{dialog.state.step === EDataImportDialogStep.File && (
{dialog.state.step !== EDataImportDialogStep.Processor && (
<div className="tw:flex tw:ml-auto tw:gap-2">
<Button type="button" variant="secondary" onClick={dialog.stepBack}>
{translate('ui_stepper_back')}
</Button>
<Button
type="button"
loading={driverConfigurationResource.isLoading()}
disabled={!dialog.state.file || !dialog.state.selectedProcessor}
onClick={() => resolveDialog({ file: dialog.state.file!, processorId: dialog.state.selectedProcessor!.id })}
onClick={isSettingsStep ? importData : () => dialog.goToSettings(driverConfiguration)}
>
{translate('ui_import')}
{translate(isSettingsStep ? 'ui_import' : 'ui_stepper_next')}
</Button>
</div>
)}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,4 +8,5 @@
export enum EDataImportDialogStep {
Processor,
File,
Settings,
}
@@ -1,11 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 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 { DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
import type { DataTransferImportSettings, DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
import type { EDataImportDialogStep } from './EDataImportDialogStep.js';
@@ -13,4 +13,5 @@ export interface IDataImportDialogState {
step: EDataImportDialogStep;
file: File | null;
selectedProcessor: DataTransferProcessorInfo | null;
settings: DataTransferImportSettings;
}
@@ -0,0 +1,72 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { Combobox, Container, FieldCheckbox, Link, useTranslate } from '@cloudbeaver/core-blocks';
import type { DataTransferImportSettings } from '@cloudbeaver/core-sdk';
import type { IDataImportDriverConfiguration } from '../DataImportDriverConfigurationResource.js';
interface Props {
settings: DataTransferImportSettings;
driverConfiguration: IDataImportDriverConfiguration;
}
export const ImportSettingsForm = observer<Props>(function ImportSettingsForm({ settings, driverConfiguration }) {
const translate = useTranslate();
const { supportedInsertReplaceMethods, supportsBulkLoad, supportsTransactions } = driverConfiguration;
useEffect(() => {
if (settings.useBulkLoad) {
settings.onDuplicateKeyMethod = undefined;
}
}, [settings.useBulkLoad]);
return (
<Container gap parent>
{supportedInsertReplaceMethods.length > 0 && (
<div>
<Combobox
name="onDuplicateKeyMethod"
state={settings}
items={supportedInsertReplaceMethods}
keySelector={method => method.id}
valueSelector={method => method.name}
titleSelector={method => method.description ?? undefined}
disabled={settings.useBulkLoad}
title={translate('plugin_data_import_settings_on_duplicate_key_title')}
placeholder={translate('plugin_data_import_settings_on_duplicate_key_placeholder')}
allowClear
>
{translate('plugin_data_import_settings_on_duplicate_key')}
</Combobox>
<Link className="tw:text-xs" href="https://dbeaver.com/docs/cloudbeaver/Data-Import-and-Replace/" target="_blank">
{translate('plugin_data_import_settings_on_duplicate_key_help')}
</Link>
</div>
)}
<Container vertical>
{supportsBulkLoad && (
<FieldCheckbox title={translate('plugin_data_import_settings_use_bulk_load_title')} name="useBulkLoad" state={settings}>
{translate('plugin_data_import_settings_use_bulk_load')}
</FieldCheckbox>
)}
{supportsTransactions && (
<FieldCheckbox title={translate('plugin_data_import_settings_use_transactions_title')} name="useTransactions" state={settings}>
{translate('plugin_data_import_settings_use_transactions')}
</FieldCheckbox>
)}
<FieldCheckbox title={translate('plugin_data_import_settings_open_new_connection_title')} name="openNewConnection" state={settings}>
{translate('plugin_data_import_settings_open_new_connection')}
</FieldCheckbox>
</Container>
</Container>
);
});
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,14 +8,16 @@
import { action, observable } from 'mobx';
import { useObservableRef } from '@cloudbeaver/core-blocks';
import type { DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
import type { DataTransferImportSettings, DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
import type { IDataImportDriverConfiguration } from '../DataImportDriverConfigurationResource.js';
import { EDataImportDialogStep } from './EDataImportDialogStep.js';
import type { IDataImportDialogState } from './IDataImportDialogState.js';
interface IDialog {
state: IDataImportDialogState;
stepBack: () => void;
goToSettings: (configuration: IDataImportDriverConfiguration | null) => void;
selectProcessor: (processor: DataTransferProcessorInfo) => void;
deleteFile: () => void;
reset: () => void;
@@ -25,17 +27,46 @@ const DEFAULT_STATE_GETTER: () => IDataImportDialogState = () => ({
step: EDataImportDialogStep.Processor,
file: null,
selectedProcessor: null,
settings: {},
});
export function useDataImportDialog(initialState?: IDataImportDialogState) {
const dialog = useObservableRef<IDialog>(
function getDefaultSettings(configuration: IDataImportDriverConfiguration): DataTransferImportSettings {
const settings: DataTransferImportSettings = {
openNewConnection: true,
};
if (configuration.supportsTransactions) {
settings.useTransactions = true;
}
if (configuration.supportedInsertReplaceMethods) {
settings.onDuplicateKeyMethod = undefined;
}
if (configuration.supportsBulkLoad) {
settings.useBulkLoad = false;
}
return settings;
}
export function useDataImportDialog(initialState?: IDataImportDialogState): IDialog {
return useObservableRef<IDialog>(
() => ({
state: initialState ?? DEFAULT_STATE_GETTER(),
stepBack() {
if (this.state.step === EDataImportDialogStep.File) {
if (this.state.step === EDataImportDialogStep.Settings) {
this.state.step = EDataImportDialogStep.File;
} else if (this.state.step === EDataImportDialogStep.File) {
this.state.step = EDataImportDialogStep.Processor;
}
},
goToSettings(configuration: IDataImportDriverConfiguration | null) {
if (configuration) {
this.state.settings = { ...getDefaultSettings(configuration), ...this.state.settings };
}
this.state.step = EDataImportDialogStep.Settings;
},
selectProcessor(processor: DataTransferProcessorInfo) {
if (this.state.selectedProcessor && this.state.selectedProcessor.id !== processor.id) {
this.reset();
@@ -51,9 +82,14 @@ export function useDataImportDialog(initialState?: IDataImportDialogState) {
this.state = DEFAULT_STATE_GETTER();
},
}),
{ state: observable, stepBack: action.bound, selectProcessor: action.bound, deleteFile: action.bound, reset: action.bound },
{
state: observable,
stepBack: action.bound,
goToSettings: action.bound,
selectProcessor: action.bound,
deleteFile: action.bound,
reset: action.bound,
},
false,
);
return dialog;
}
@@ -0,0 +1,79 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { toJS } from 'mobx';
import {
CONNECTION_INFO_PARAM_SCHEMA,
ConnectionInfoResource,
createConnectionParam,
type IConnectionInfoParams,
isConnectionInfoParamEqual,
} from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { CachedMapResource, isResourceAlias, type ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { type DriverConfigurationFragment, type GetConnectionImportDriverConfigurationQuery, GraphQLService } from '@cloudbeaver/core-sdk';
import { schemaValidationError } from '@cloudbeaver/core-utils';
export type IDataImportDriverConfiguration = DriverConfigurationFragment;
@injectable(() => [GraphQLService, ConnectionInfoResource])
export class DataImportDriverConfigurationResource extends CachedMapResource<IConnectionInfoParams, IDataImportDriverConfiguration> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly connectionInfoResource: ConnectionInfoResource,
) {
super();
this.sync(connectionInfoResource);
connectionInfoResource.onItemDelete.addHandler(this.delete.bind(this));
}
override isKeyEqual(param: IConnectionInfoParams, second: IConnectionInfoParams): boolean {
return isConnectionInfoParamEqual(param, second);
}
protected async loader(originalKey: ResourceKey<IConnectionInfoParams>): Promise<Map<IConnectionInfoParams, IDataImportDriverConfiguration>> {
if (isResourceAlias(originalKey)) {
throw new Error('Aliases are not supported by this resource');
}
const connectionsList: GetConnectionImportDriverConfigurationQuery['connections'] = [];
await ResourceKeyUtils.forEachAsync(originalKey, async key => {
if (!this.connectionInfoResource.isConnected(key)) {
throw new Error(`Connection is not connected (${key.projectId}, ${key.connectionId})`);
}
const { connections } = await this.graphQLService.sdk.getConnectionImportDriverConfiguration({
projectId: key.projectId,
connectionId: key.connectionId,
});
if (!connections.some(connection => connection.id === key.connectionId)) {
throw new Error(`Connection is not found (${key.connectionId})`);
}
connectionsList.push(...connections);
});
this.set(
resourceKeyList(connectionsList.map(createConnectionParam)),
connectionsList.map(connection => connection.driverConfiguration),
);
return this.data;
}
protected validateKey(key: IConnectionInfoParams): boolean {
const parse = CONNECTION_INFO_PARAM_SCHEMA.safeParse(toJS(key));
if (!parse.success) {
this.logger.warn(`Invalid resource key ${schemaValidationError(parse.error).toString()}`);
}
return parse.success;
}
}
@@ -8,10 +8,11 @@
import { computed, makeObservable } from 'mobx';
import { ProcessSnackbar } from '@cloudbeaver/core-blocks';
import type { IConnectionInfoParams } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { AsyncTaskInfoService, EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
import { GraphQLService } from '@cloudbeaver/core-sdk';
import { type DataTransferImportSettings, GraphQLService } from '@cloudbeaver/core-sdk';
import { getProgressPercent } from '@cloudbeaver/core-utils';
import { DataImportSettingsService } from './DataImportSettingsService.js';
@@ -38,7 +39,14 @@ export class DataImportService {
});
}
async importData(connectionId: string, contextId: string, projectId: string, resultsId: string, processorId: string, file: File): Promise<boolean> {
async importData(
connectionKey: IConnectionInfoParams,
contextId: string,
resultsId: string,
processorId: string,
file: File,
settings?: DataTransferImportSettings,
): Promise<boolean> {
const abortController = new AbortController();
let cancelImplementation: (() => void | Promise<void>) | null;
let isCancelled = false;
@@ -65,11 +73,11 @@ export class DataImportService {
const { taskInfo } = await this.graphQLService.sdk.dataTransferImportDataIntoResults(
{
connectionId,
contextId,
projectId,
connectionId: connectionKey.connectionId,
projectId: connectionKey.projectId,
resultsId,
parameters: { processorId },
parameters: { processorId, settings },
},
undefined,
abortController.signal,
@@ -4,4 +4,24 @@ export default [
['plugin_data_import_process_success', 'Data imported successfully'],
['plugin_data_import_process_fail', 'Data import failed'],
['plugin_data_import_process_file_processing_step_message', 'File uploaded, processing...'],
['plugin_data_import_settings_on_duplicate_key', 'On duplicate key'],
['plugin_data_import_settings_use_bulk_load', 'Use bulk load'],
['plugin_data_import_settings_use_bulk_load_title', 'Use bulk load for faster import'],
['plugin_data_import_settings_use_transactions', 'Use transactions'],
[
'plugin_data_import_settings_use_transactions_title',
'Execute the import within a database transaction. If the import fails, all changes can be rolled back',
],
[
'plugin_data_import_settings_on_duplicate_key_title',
'Choose how to handle rows that already exist in the target table based on a primary or unique key',
],
['plugin_data_import_settings_on_duplicate_key_placeholder', 'None'],
['plugin_data_import_settings_open_new_connection', 'Open new connection'],
[
'plugin_data_import_settings_open_new_connection_title',
'Use this option to speed up data transfer. If selected, a new connection will be opened and the data transfer will not interfere with other calls to the database where data is being transferred to.',
],
['plugin_data_import_settings_on_duplicate_key_help', 'Learn more about the "On duplicate key" setting'],
];
@@ -4,4 +4,23 @@ export default [
['plugin_data_import_process_success', 'Données importées avec succès'],
['plugin_data_import_process_fail', "Échec de l'importation des données"],
['plugin_data_import_process_file_processing_step_message', 'Fichier téléchargé, en cours de traitement...'],
['plugin_data_import_settings_on_duplicate_key', 'En cas de clé dupliquée'],
[
'plugin_data_import_settings_on_duplicate_key_title',
"Choisissez comment gérer les lignes qui existent déjà dans la table cible en fonction d'une clé primaire ou unique",
],
['plugin_data_import_settings_use_bulk_load_title', 'Utiliser le chargement en masse pour un import plus rapide'],
['plugin_data_import_settings_use_bulk_load', 'Utiliser le chargement en masse'],
['plugin_data_import_settings_use_transactions', 'Utiliser les transactions'],
[
'plugin_data_import_settings_use_transactions_title',
"Exécuter l'importation dans une transaction de base de données. Si l'importation échoue, toutes les modifications peuvent être annulées",
],
['plugin_data_import_settings_on_duplicate_key_placeholder', 'Aucune'],
['plugin_data_import_settings_open_new_connection', 'Ouvrir une nouvelle connexion'],
[
'plugin_data_import_settings_open_new_connection_title',
"Utilisez cette option pour accélérer le transfert de données. Si elle est sélectionnée, une nouvelle connexion sera ouverte et le transfert de données n'interférera pas avec les autres appels à la base de données vers laquelle les données sont transférées.",
],
['plugin_data_import_settings_on_duplicate_key_help', 'En savoir plus sur le paramètre "En cas de clé dupliquée"'],
];
@@ -4,4 +4,24 @@ export default [
['plugin_data_import_process_success', 'Data imported successfully'],
['plugin_data_import_process_fail', 'Data import failed'],
['plugin_data_import_process_file_processing_step_message', 'File uploaded, processing...'],
['plugin_data_import_settings_on_duplicate_key', 'On duplicate key'],
['plugin_data_import_settings_use_bulk_load', 'Use bulk load'],
['plugin_data_import_settings_use_bulk_load_title', 'Use bulk load for faster import'],
['plugin_data_import_settings_use_transactions', 'Use transactions'],
[
'plugin_data_import_settings_use_transactions_title',
'Execute the import within a database transaction. If the import fails, all changes can be rolled back',
],
[
'plugin_data_import_settings_on_duplicate_key_title',
'Choose how to handle rows that already exist in the target table based on a primary or unique key',
],
['plugin_data_import_settings_on_duplicate_key_placeholder', 'None'],
['plugin_data_import_settings_open_new_connection', 'Open new connection'],
[
'plugin_data_import_settings_open_new_connection_title',
'Use this option to speed up data transfer. If selected, a new connection will be opened and the data transfer will not interfere with other calls to the database where data is being transferred to',
],
['plugin_data_import_settings_on_duplicate_key_help', 'Learn more about the "On duplicate key" setting'],
];
@@ -4,4 +4,24 @@ export default [
['plugin_data_import_process_success', 'Данные успешно импортированы'],
['plugin_data_import_process_fail', 'Ошибка импорта данных'],
['plugin_data_import_process_file_processing_step_message', 'Файл загружен, обработка...'],
['plugin_data_import_settings_on_duplicate_key', 'При дублировании ключа'],
[
'plugin_data_import_settings_on_duplicate_key_title',
'Выберите, как обрабатывать строки, которые уже существуют в целевой таблице на основе первичного или уникального ключа',
],
['plugin_data_import_settings_use_bulk_load', 'Использовать массовую загрузку'],
['plugin_data_import_settings_use_bulk_load_title', 'Использовать массовую загрузку для более быстрого импорта'],
['plugin_data_import_settings_use_transactions', 'Использовать транзакции'],
[
'plugin_data_import_settings_use_transactions_title',
'Выполнять импорт в рамках транзакции базы данных. Если импорт не удается, все изменения могут быть отменены',
],
['plugin_data_import_settings_on_duplicate_key_placeholder', 'Нет'],
['plugin_data_import_settings_open_new_connection', 'Открыть новое соединение'],
[
'plugin_data_import_settings_open_new_connection_title',
'Используйте эту опцию, чтобы ускорить передачу данных. Если выбран этот параметр, будет открыто новое соединение, и передача данных не будет мешать другим вызовам к базе данных, в которую передаются данные',
],
['plugin_data_import_settings_on_duplicate_key_help', 'Узнать больше о параметре "При дублировании ключа"'],
];
@@ -4,4 +4,23 @@ export default [
['plugin_data_import_process_success', 'Dữ liệu đã được nhập thành công'],
['plugin_data_import_process_fail', 'Nhập dữ liệu thất bại'],
['plugin_data_import_process_file_processing_step_message', 'Tệp đã được tải lên, đang xử lý...'],
['plugin_data_import_settings_on_duplicate_key', 'Khi trùng lặp khóa'],
[
'plugin_data_import_settings_on_duplicate_key_title',
'Chọn cách xử lý các hàng đã tồn tại trong bảng đích dựa trên khóa chính hoặc khóa duy nhất',
],
['plugin_data_import_settings_use_bulk_load', 'Sử dụng tải hàng loạt'],
['plugin_data_import_settings_use_bulk_load_title', 'Sử dụng tải hàng loạt để nhập nhanh hơn'],
['plugin_data_import_settings_use_transactions', 'Sử dụng giao dịch'],
[
'plugin_data_import_settings_use_transactions_title',
'Thực hiện nhập trong một giao dịch cơ sở dữ liệu. Nếu nhập thất bại, tất cả các thay đổi có thể được hoàn tác',
],
['plugin_data_import_settings_on_duplicate_key_placeholder', 'Không có'],
['plugin_data_import_settings_open_new_connection', 'Mở kết nối mới'],
[
'plugin_data_import_settings_open_new_connection_title',
'Sử dụng tùy chọn này để tăng tốc độ truyền dữ liệu. Nếu được chọn, một kết nối mới sẽ được mở và việc truyền dữ liệu sẽ không ảnh hưởng đến các cuộc gọi khác đến cơ sở dữ liệu nơi dữ liệu đang được truyền đến',
],
['plugin_data_import_settings_on_duplicate_key_help', 'Tìm hiểu thêm về cài đặt "Khi trùng lặp khóa"'],
];
@@ -4,4 +4,17 @@ export default [
['plugin_data_import_process_success', '数据导入成功'],
['plugin_data_import_process_fail', '数据导入失败'],
['plugin_data_import_process_file_processing_step_message', '文件已上传,正在处理...'],
['plugin_data_import_settings_on_duplicate_key', '当重复键时'],
['plugin_data_import_settings_on_duplicate_key_title', '选择如何处理目标表中已存在的行,基于主键或唯一键'],
['plugin_data_import_settings_use_bulk_load', '使用批量加载'],
['plugin_data_import_settings_use_bulk_load_title', '使用批量加载以更快地导入数据'],
['plugin_data_import_settings_use_transactions', '使用事务'],
['plugin_data_import_settings_use_transactions_title', '在数据库事务中执行导入。如果导入失败,所有更改都可以回滚'],
['plugin_data_import_settings_on_duplicate_key_placeholder', '无'],
['plugin_data_import_settings_open_new_connection', '打开新连接'],
[
'plugin_data_import_settings_open_new_connection_title',
'使用此选项可加快数据传输。如果选中,将打开一个新连接,数据传输不会干扰对目标数据库的其他调用。',
],
['plugin_data_import_settings_on_duplicate_key_help', '了解有关“当重复键时”设置的更多信息'],
];
@@ -10,6 +10,7 @@ import { Dependency, ModuleRegistry, proxy, Bootstrap } from '@cloudbeaver/core-
import { LocaleService } from './LocaleService.js';
import { DataImportService } from './DataImportService.js';
import { DataImportProcessorsResource } from './DataImportProcessorsResource.js';
import { DataImportDriverConfigurationResource } from './DataImportDriverConfigurationResource.js';
import { DataImportSettingsService } from './DataImportSettingsService.js';
import { DataImportBootstrap } from './DataImportBootstrap.js';
@@ -25,6 +26,7 @@ export default ModuleRegistry.add({
.addSingleton(Bootstrap, LocaleService)
.addSingleton(DataImportService)
.addSingleton(DataImportProcessorsResource)
.addSingleton(DataImportDriverConfigurationResource)
.addSingleton(DataImportSettingsService);
},
});
@@ -16,6 +16,9 @@
{
"path": "../core-cli"
},
{
"path": "../core-connections"
},
{
"path": "../core-di"
},
+1 -1
View File
@@ -8,7 +8,7 @@
"public/**/*",
"src/**/*.css"
],
"version": "26.1.5",
"version": "26.2.0",
"description": "CloudBeaver Community",
"license": "DBeaver Corp",
"exports": {
+87 -47
View File
@@ -3176,6 +3176,7 @@ __metadata:
dependencies:
"@cloudbeaver/core-blocks": "workspace:*"
"@cloudbeaver/core-cli": "workspace:*"
"@cloudbeaver/core-connections": "workspace:*"
"@cloudbeaver/core-di": "workspace:*"
"@cloudbeaver/core-dialogs": "workspace:*"
"@cloudbeaver/core-events": "workspace:*"
@@ -11795,6 +11796,15 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:2":
version: 2.6.9
resolution: "debug@npm:2.6.9"
dependencies:
ms: "npm:2.0.0"
checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589
languageName: node
linkType: hard
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.3":
version: 4.4.3
resolution: "debug@npm:4.4.3"
@@ -11807,6 +11817,15 @@ __metadata:
languageName: node
linkType: hard
"debug@npm:^3.2.6":
version: 3.2.7
resolution: "debug@npm:3.2.7"
dependencies:
ms: "npm:^2.1.1"
checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a
languageName: node
linkType: hard
"decamelize@npm:^1.2.0":
version: 1.2.0
resolution: "decamelize@npm:1.2.0"
@@ -13942,6 +13961,15 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:^0.4.4":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3"
checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4
languageName: node
linkType: hard
"iconv-lite@npm:^0.7.0":
version: 0.7.2
resolution: "iconv-lite@npm:0.7.2"
@@ -13997,15 +14025,6 @@ __metadata:
languageName: node
linkType: hard
"image-size@npm:~0.5.0":
version: 0.5.5
resolution: "image-size@npm:0.5.5"
bin:
image-size: bin/image-size.js
checksum: 10c0/655204163af06732f483a9fe7cce9dff4a29b7b2e88f5c957a5852e8143fa750f5e54b1955a2ca83de99c5220dbd680002d0d4e09140b01433520f4d5a0b1f4c
languageName: node
linkType: hard
"immutable@npm:>=5.1.8":
version: 5.1.9
resolution: "immutable@npm:5.1.9"
@@ -14838,37 +14857,37 @@ __metadata:
languageName: node
linkType: hard
"less@npm:^4.2.0":
version: 4.6.4
resolution: "less@npm:4.6.4"
"less@npm:>=4.7.0":
version: 4.8.1
resolution: "less@npm:4.8.1"
dependencies:
copy-anything: "npm:^3.0.5"
errno: "npm:^0.1.1"
graceful-fs: "npm:^4.1.2"
image-size: "npm:~0.5.0"
make-dir: "npm:^2.1.0"
make-dir: "npm:^5.1.0"
mime: "npm:^1.4.1"
needle: "npm:^3.1.0"
parse-node-version: "npm:^1.0.1"
probe-image-size: "npm:^7.2.3"
source-map: "npm:~0.6.0"
dependenciesMeta:
errno:
optional: true
graceful-fs:
optional: true
image-size:
optional: true
make-dir:
optional: true
mime:
optional: true
needle:
optional: true
probe-image-size:
optional: true
source-map:
optional: true
bin:
lessc: bin/lessc
checksum: 10c0/34fc96e617f1c8b9bff019ffcab6304550d733fdb5daaa4a69dd404d83aa8566c21d8905bbb75f025334875188f38126edd4af10604e9ee634f43f6d41430837
checksum: 10c0/41fbad950e9d5110bd86c94726a57eb771e2dfc6eb82aa687dd153f9f463c8707cb3b4e89144d1617bbf026ff7dc57cea7e7a33dfbe959565c9cc35a9d480808
languageName: node
linkType: hard
@@ -15206,13 +15225,10 @@ __metadata:
languageName: node
linkType: hard
"make-dir@npm:^2.1.0":
version: 2.1.0
resolution: "make-dir@npm:2.1.0"
dependencies:
pify: "npm:^4.0.1"
semver: "npm:^5.6.0"
checksum: 10c0/ada869944d866229819735bee5548944caef560d7a8536ecbc6536edca28c72add47cc4f6fc39c54fb25d06b58da1f8994cf7d9df7dadea047064749efc085d8
"make-dir@npm:^5.1.0":
version: 5.1.0
resolution: "make-dir@npm:5.1.0"
checksum: 10c0/b8dc9e831e9941db84b63c0ba7dc7c8c7e92704d4fada300fc86293bea6491496b95208d6f98ec78b87f004e26abf143730dd8050920b88ee4b8bbe1eb14d661
languageName: node
linkType: hard
@@ -16138,7 +16154,14 @@ __metadata:
languageName: node
linkType: hard
"ms@npm:^2.1.3":
"ms@npm:2.0.0":
version: 2.0.0
resolution: "ms@npm:2.0.0"
checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d
languageName: node
linkType: hard
"ms@npm:^2.1.1, ms@npm:^2.1.3":
version: 2.1.3
resolution: "ms@npm:2.1.3"
checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48
@@ -16192,12 +16215,12 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^3.3.16":
version: 3.3.16
resolution: "nanoid@npm:3.3.16"
"nanoid@npm:>=3.3.17":
version: 6.0.1
resolution: "nanoid@npm:6.0.1"
bin:
nanoid: bin/nanoid.cjs
checksum: 10c0/bbf2dcffe22d2b62d16de2711752070b539c0f644c7916f823ad6521986b2078cbe524f2d6240f58c46e9141ea0c7b87a029e100c0f7f175228cacaf30e41bba
nanoid: bin/nanoid.js
checksum: 10c0/5b362ad9822388dfebdf655849c76d3539e074d1003fbe6eb5dff8687faf93a9abbb19bdc5d134b9b8599f875f9b091feacfd7099b34b5fef23bc3f5be41dd10
languageName: node
linkType: hard
@@ -16208,6 +16231,19 @@ __metadata:
languageName: node
linkType: hard
"needle@npm:^2.5.2":
version: 2.9.1
resolution: "needle@npm:2.9.1"
dependencies:
debug: "npm:^3.2.6"
iconv-lite: "npm:^0.4.4"
sax: "npm:^1.2.4"
bin:
needle: ./bin/needle
checksum: 10c0/65a7eaeaf4ca3410de492957474592af9838e02875273d9232ff6cff331393c58a95e48c592bd9a05f575e5bb9b08543d6cfd19eb96595dbd3d7da2c5fe1accb
languageName: node
linkType: hard
"needle@npm:^3.1.0":
version: 3.5.0
resolution: "needle@npm:3.5.0"
@@ -17026,13 +17062,6 @@ __metadata:
languageName: node
linkType: hard
"pify@npm:^4.0.1":
version: 4.0.1
resolution: "pify@npm:4.0.1"
checksum: 10c0/6f9d404b0d47a965437403c9b90eca8bb2536407f03de165940e62e72c8c8b75adda5516c6b9b23675a5877cc0bcac6bdfb0ef0e39414cd2476d5495da40e7cf
languageName: node
linkType: hard
"possible-typed-array-names@npm:^1.0.0, possible-typed-array-names@npm:^1.1.0":
version: 1.1.0
resolution: "possible-typed-array-names@npm:1.1.0"
@@ -17181,6 +17210,17 @@ __metadata:
languageName: node
linkType: hard
"probe-image-size@npm:^7.2.3":
version: 7.3.0
resolution: "probe-image-size@npm:7.3.0"
dependencies:
lodash.merge: "npm:^4.6.2"
needle: "npm:^2.5.2"
stream-parser: "npm:~0.3.1"
checksum: 10c0/92a1c11fbb6149c0122852a179a54d9edda9f7a5084e837aa529d30b7a213120771356e72547bb18a4e183c8896b83b53380b232ed0a97594d2d80c97cf9a127
languageName: node
linkType: hard
"proc-log@npm:^6.0.0":
version: 6.1.0
resolution: "proc-log@npm:6.1.0"
@@ -18229,7 +18269,7 @@ __metadata:
languageName: node
linkType: hard
"safer-buffer@npm:>= 2.1.2 < 3.0.0":
"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0":
version: 2.1.2
resolution: "safer-buffer@npm:2.1.2"
checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4
@@ -18281,15 +18321,6 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^5.6.0":
version: 5.7.2
resolution: "semver@npm:5.7.2"
bin:
semver: bin/semver
checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25
languageName: node
linkType: hard
"semver@npm:^6.3.1":
version: 6.3.1
resolution: "semver@npm:6.3.1"
@@ -18697,6 +18728,15 @@ __metadata:
languageName: node
linkType: hard
"stream-parser@npm:~0.3.1":
version: 0.3.1
resolution: "stream-parser@npm:0.3.1"
dependencies:
debug: "npm:2"
checksum: 10c0/585508801423bd6c53f6dda9d78e4b743a08ab72e8e2680431fa855ef950e59c849ec2838f2a00c59af655ff8463e90f660f9169a816e63a3ca159cf713bae5c
languageName: node
linkType: hard
"strict-event-emitter@npm:^0.5.1":
version: 0.5.1
resolution: "strict-event-emitter@npm:0.5.1"