mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Merge branch 'devel' into dbeaver/pro#9335-Not-keep-metadata-queries-in-QMDB
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
+2
-1
@@ -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")
|
||||
}
|
||||
|
||||
+41
-24
@@ -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),
|
||||
|
||||
+6
@@ -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(
|
||||
|
||||
+30
@@ -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(
|
||||
|
||||
+6
@@ -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),
|
||||
|
||||
+27
-13
@@ -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>
|
||||
|
||||
+2
@@ -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 {
|
||||
|
||||
+4
-1
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
+44
-1
@@ -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)
|
||||
}
|
||||
+9
@@ -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
|
||||
}
|
||||
+5
@@ -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'],
|
||||
|
||||
+17
-2
@@ -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>
|
||||
|
||||
+14
-1
@@ -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) {
|
||||
|
||||
+29
@@ -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>
|
||||
);
|
||||
});
|
||||
+32
@@ -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>
|
||||
);
|
||||
});
|
||||
+3
-35
@@ -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;
|
||||
}
|
||||
|
||||
+11
-11
@@ -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>
|
||||
|
||||
|
||||
+15
@@ -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"
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"public/**/*",
|
||||
"src/**/*.css"
|
||||
],
|
||||
"version": "26.1.5",
|
||||
"version": "26.2.0",
|
||||
"description": "CloudBeaver Community",
|
||||
"license": "DBeaver Corp",
|
||||
"exports": {
|
||||
|
||||
+87
-47
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user