Compare commits

...
Author SHA1 Message Date
Alexander Skoblikovandkseniaguzeeva d7ded85ff0 dbeaver/pro#9508 use proto header without host (#4379)
* dbeaver/pro#9508 use proto header without host

* dbeaver/pro#9508 review fixes

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-06-10 09:17:54 +02:00
Ainur 6364581b5b dbeaver/pro#9511 fix unknown struct presentation 2026-06-04 14:37:41 +02:00
Serge Rider bfcc7764d8 Merge branch 'release_26_1_0' of https://github.com/dbeaver/cloudbeaver into release_26_1_0 2026-06-04 14:37:35 +02:00
Sychev Andrey 798adb4787 dbeaver/pro#9438 fix: fix the export lib version (#4369) (#4372)
* dbeaver/pro#9438 fix: fix the export lib version

* dbeaver/pro#9438 fix: update modern-screenshot dependency to version 4.6.8
2026-06-01 15:10:59 +02:00
Ainur 211ca65acb dbeaver/pro#9447 validate project path fix (#4367)
* dbeaver/pro#9447 validate project path fix

* dbeaver/pro#9447 validate project name on creating project

* dbeaver/pro#9447 validate project using general utils
2026-05-29 10:27:42 +02:00
Sychev AndreyandEvgenia 4c71bd6005 dbeaver/pro#9407 fix: long values wrapping (#4364)
we mimic here the old input's behaviour, when we can't see the scroll but can navigate through the 1-line value with keyboard or mouse drag/selection

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-27 20:38:46 +02:00
Serge Riderandkseniaguzeeva 74a5877e6d dbeaver/pro#9397 Initialize datasource registry flow fix (#4360)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2026-05-26 17:47:26 +02:00
sergeyteleshevandEvgenia 961430f988 [CB] Menu is displayed under opened forms in the Administration page (#4358)
* dbeaver/pro#9362 fixes z-indexes issues for dialogs and notifications

* moves getPersistentElements to dialog level so all dialogs can handle it

* adds data-dialog-persistent-notification

* fixes resize controls layering

* fixes issue with panel buttons layering

* cleanup

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-25 17:53:03 +02:00
759b30a24b dbeaver/pro#9182 remove global secret controller for server apps (#4340)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
2026-05-25 17:52:50 +02:00
Sychev AndreyandEvgenia 225ce91fd7 dbeaver/pro#8486 feat: add DataReadonlyReason API for data sources (#4354)
* dbeaver/pro#8486 feat: add DataReadonlyReason API for data sources

Previously TableStatusIndicator relied on model.isReadonly() which lost reasons. This caused the "Read-only connection" tooltip to show in grouping panel even on
editable connections — the connection wasn't read-only, the
presentation was.

Each data source now declares its specific reason via getReadonlyReason(). UI maps the typed reason to an accurate
message

* dbeaver/pro#8486 refactor: remove DataReadonlyReason references and use hasFeature flag

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-25 12:36:20 +02:00
f7bdd80e9f dbeaver/pro#9131 use visual keys for copy & paste (#4350)
Co-authored-by: sergeyteleshev <iamsergeyteleshev@gmail.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-22 16:55:18 +02:00
sergeyteleshevandDaria Marutkina e42d34ac58 [CB] Ability to add a new MCP agent (#4329)
* dbeaver/pro#9126 initial commit for PR message

* adds validations

* adds json linter

* reverts safeParseSchema

* removes reset button

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-22 10:11:59 +02:00
sergeyteleshevandDaria Marutkina 89a1af2df9 [CB] Fix table spacing regression in admin UI (#4353)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-21 19:49:34 +02:00
sergeyteleshevandDaria Marutkina e4260b4cd2 [CB] API token generation dropdown doesn't fit to the island (#4351)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-21 19:47:45 +02:00
AinurandEvgenia 956ebaa515 dbeaver/pro#9306 disconnect event send fix (#4356)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-21 19:45:55 +02:00
alexandDaria Marutkina bccf47020e dbeaver/pro#9095 load active connections before checking execution context (#4355)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-21 18:22:42 +02:00
alexandEvgenia dcd2b608ea dbeaver/pro#9370 check all passwords, not only the first one (#4357)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-21 18:20:29 +02:00
84af14d2f7 Closes dbeaver/pro#9327 Extract LibSQL driver from build (#4344)
* Closes dbeaver/pro#9327 Extract LibSQL driver from build

* dbeaver/pro#9327 load libsql from maven for cloudbeaver ce

---------

Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-21 14:52:16 +02:00
sergeyteleshevandEvgenia f513f7dc44 [CB] Many warnings in the console when creating a new connection (#4352)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-21 14:50:12 +02:00
Sychev AndreyandDaria Marutkina ffa7f730de dbeaver/pro#8741 fix: update container label visibility logic when no… (#4347)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-21 12:24:15 +02:00
Sychev AndreyandDaria Marutkina 694d3b241f dbeaver/pro#9342 build: update node version to 24.13.1 in CI workflows (#4349)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-20 21:38:49 +02:00
9c8f3c47e6 [CB] Add shortcut for the cell context menu in data and metadata editors (#4332)
* dbeaver/pro#8424 adds shortcut top open/close context menu for the data editor

* moves helper into parseHotkeys + simplifies it

* adds unit tests

* adds autoFocusOnShow prop to support backward compatibility

* dbeaver/pro#8424 use current state

* adds few test cases for the extra buttons pressed

---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-20 16:38:03 +02:00
sergeyteleshev f918c9e25e dbeaver/pro#8621 hides upload button for viewers roles and for disabled edit setting in data editor (#4348) 2026-05-20 16:37:43 +02:00
b84031bb84 dbeaver/cloudbeaver#4138 feat: support inline editing of multi-line v… (#4341)
* dbeaver/cloudbeaver#4138 feat: support inline editing of multi-line values in data grid cells

- TextEditor: switch <input> to <textarea rows=1> so newlines are preserved when editing. Suppress the trailing insertLineBreak from the Enter that opens the editor (would otherwise wipe the auto-selected value).
- ResultSetFormatAction.truncateText: substitute \n, \r, \r\n, \t with
  visible glyphs (↵, ␍, →) and collapse other control chars to a space
  in a single pass.

Perf note: the new replace should be faster than the prior
split('') + map + join. No intermediate array allocations, and the
callback runs only on matched chars (which are sparse in display text). \p{Cc} also expands coverage from charCode < 32 to include DEL and C1 controls (U+007F, U+0080–U+009F). (closer to Dbeaver behavior)

* dbeaver/pro#4138 feat: show raw text in title for text cells

* dbeaver/pro#4138 treat one cell differently

---------

Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: alex <48489896+devnaumov@users.noreply.github.com>
2026-05-20 14:28:08 +02:00
af651f9d10 dbeaver/pro#8459 trim username (#4343)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-19 19:00:53 +02:00
sergeyteleshevandDaria Marutkina 3245943f3d [CB] It is impossible to open edit forms from entity tables via keyboard (Users, Teams, Drivers, Providers, Secret Management) (#4345)
* dbeaver/pro#8404 adds onCellKeyDownTableOpen helper to all side modal tables

* replaces onKeyDown with Command

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-19 12:10:59 +02:00
Sychev AndreyandEvgenia fe01b23fce dbeaver/pro#9090 refactor: use new getters for sql execution plan (#4333)
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
2026-05-18 17:36:20 +02:00
alexandDaria Marutkina 8ec052e1b1 dbeaver/pro#9274 update whereFilter even if there is no result (#4336)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-18 13:48:51 +02:00
Daria Marutkina b10b407270 dbeaver/pro#9290 Update README file (#4346)
Added changelog for version 26.0.5, detailing new features, fixes, and updates across various components including authorization, data editor, SQL editor, database drivers, and security improvements.
2026-05-18 12:30:10 +02:00
alexandDaria Marutkina 5f5a6743eb dbeaver/pro#9133 use common GridSelectAction (#4342)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2026-05-18 11:22:26 +02:00
sergeyteleshev 4b30dc3ba5 [CB] Refresh of disconnected connection leads to 'Something went wrong' error (#4338)
* dbeaver/pro#8294 allows to refresh only connection root nodes for non-established connections

* renames variable

* checks if connection is connected to refresh it properly

* adds node check instead of connection directly
2026-05-15 13:27:33 +02:00
Ruslan Musaev 86e114d471 dbeaver/pro#9134 fix ldap error log (#4339) 2026-05-15 11:34:15 +02:00
dbeaver-devops d4d2e2d1fa dbeaver/pro#9290 26.1.0 version bump 2026-05-12 19:52:53 +02:00
123 changed files with 790 additions and 192 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
node-version: "24.13.1"
cache: "yarn"
cache-dependency-path: |
cloudbeaver/webapp/yarn.lock
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
- run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: "lts/*"
node-version: "24.13.1"
cache: "yarn"
cache-dependency-path: "./cloudbeaver/webapp/yarn.lock"
+25
View File
@@ -25,6 +25,31 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 26.0.5 2026-05-18
- Authorization:
- Added HTTP header authentication support for GraphQL queries. Users can provide API tokens via Authorization or X-Api-Key headers, allowing external data tools to authenticate without modifying request bodies.
- Data Editor:
- Added a DDL generation option to the Data Editor and Result Set. Users can view table schemas directly while editing or reviewing records.
- Fixed the copying of Boolean values in the clipboard in the Data Editor.
- SQL Editor:
- Updated the SQL Editor to use real tab characters for indentation. The application displayed tabs as four-space gaps with a distinct visual marker.
- Added an ability to view and edit scripts without connecting to the database.
- Added more settings to the Generated SQL window, including the ability to select fully qualified names for query or use compact SQL formatting.
- Added the ability to open generated queries right from the Generate SQL window.
- General:
- Updated the manifest.webmanifest file to dynamically generate the environment variable according to the CLOUDBEAVER_ROOT_URI. (thanks to @houssemexo26 )
- Databases:
- PostgreSQL:
- Updated driver to version 42.7.11.
- Fixed missing INOUT parameters in CHECK generation for procedures and functions.
- Fixed handling of 24:xx values in time columns.
- Snowflake: Updated driver to version 4.1.0.
- SQL Server: Fixed the issue with connecting to the database after the Java update.
- Security:
- Added an administrative setting to restrict SSH tunneling capabilities. Administrators can now limit tunnel configuration to authorized users, reducing the risk of unauthorized network access.
- Fixed the high vulnerability (CVE-2026-42035) in the axios library. The library was updated to version 1.15.1.
- Fixed the medium vulnerability (CVE-2026-41305) in the postcss library. The library was updated to version 8.5.14.
### 26.0.4 2026-05-04
- Data Editor:
-1
View File
@@ -18,7 +18,6 @@ cd ../..
echo "Pull dbeaver platform"
[ ! -d dbeaver ] && git clone --depth 1 https://github.com/dbeaver/dbeaver.git
[ ! -d dbeaver-common ] && git clone --depth 1 https://github.com/dbeaver/dbeaver-common.git
[ ! -d dbeaver-jdbc-libsql ] && git clone --depth 1 https://github.com/dbeaver/dbeaver-jdbc-libsql.git
cd cloudbeaver/deploy
-1
View File
@@ -20,7 +20,6 @@ echo Pull dbeaver platform
IF NOT EXIST dbeaver git clone https://github.com/dbeaver/dbeaver.git
IF NOT EXIST dbeaver-common git clone https://github.com/dbeaver/dbeaver-common.git
IF NOT EXIST dbeaver-jdbc-libsql git clone https://github.com/dbeaver/dbeaver-jdbc-libsql.git
cd cloudbeaver\deploy
-1
View File
@@ -1,3 +1,2 @@
dbeaver-common
dbeaver-jdbc-libsql
dbeaver
@@ -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.10.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.11.qualifier
Bundle-Release-Date: 20260601
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.10-SNAPSHOT</version>
<version>1.0.11-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.101.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.102.qualifier
Bundle-Release-Date: 20260601
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.data.gis;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.101-SNAPSHOT</version>
<version>1.0.102-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -377,6 +377,11 @@ public class WebGlobalProjectRegistryProxy implements DBPDataSourceRegistry, Dat
return dataSourceRegistry.getPreferenceStore();
}
@Override
public void initializeDataSources() {
dataSourceRegistry.initializeDataSources();
}
@Override
public DataSourceParseResults loadDataSources(
@NotNull List<DBPDataSourceConfigurationStorage> storages,
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -252,14 +252,14 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
}
case WSDataSourceEvent.UPDATED -> {
if (event.getProperty() == WSDataSourceProperty.CONFIGURATION) {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
WebDataSourceUtils.disconnectDataSource(webSession, ds, true);
}
if (event.getProperty() != WSDataSourceProperty.INTERNAL) {
sendDataSourceUpdatedEvent = true;
}
}
case WSDataSourceEvent.DELETED -> {
WebDataSourceUtils.disconnectDataSource(webSession, ds);
WebDataSourceUtils.disconnectDataSource(webSession, ds, false);
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
dsrc.removeDataSourceFromList(ds);
}
@@ -345,7 +345,7 @@ public class WebSessionProjectImpl extends WebProjectImpl implements DBPAdaptabl
@NotNull
private WebConnectionInfo closeAndDeleteConnection(@NotNull WebConnectionInfo connectionInfo) throws DBWebException {
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer, false);
DBPDataSourceRegistry registry = getDataSourceRegistry();
registry.removeDataSource(dataSourceContainer);
removeConnection(dataSourceContainer);
@@ -53,6 +53,7 @@ import org.jkiss.dbeaver.registry.DataSourceParseResults;
import org.jkiss.dbeaver.registry.ResourceTypeDescriptor;
import org.jkiss.dbeaver.registry.ResourceTypeRegistry;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import org.jkiss.utils.IOUtils;
import org.jkiss.utils.Pair;
@@ -283,7 +284,7 @@ public class LocalResourceController extends BaseLocalResourceController {
throw new DBException("Error creating shared project path", e);
}
}
validateResourcePath(name);
GeneralUtils.validateResourceNameUnconditionally(name);
validateProjectName(null, name);
var projectPath = sharedProjectsPath.resolve(name);
if (Files.exists(projectPath)) {
@@ -1106,6 +1107,7 @@ public class LocalResourceController extends BaseLocalResourceController {
RMProjectName project = WebRMUtils.parseProjectName(projectId);
RMProjectType type = project.getType();
String projectName = project.getName();
GeneralUtils.validateResourceNameUnconditionally(projectName);
switch (type) {
case GLOBAL:
if (!projectName.equals(globalProjectName)) {
@@ -31,6 +31,6 @@ public class ServerGlobalWorkspaceSession extends LocalWorkspaceSession {
@Nullable
@Override
public DBSSecretController getSecretController() {
return super.getSecretController();
return null;
}
}
@@ -319,8 +319,7 @@ public class ServletAppUtils {
if (log.isTraceEnabled()) {
log.trace("forwarded origin: " + origin);
}
}
if (CommonUtils.isEmpty(origin)) {
} else if (CommonUtils.isEmpty(origin)) {
URI requestUrl = URI.create(request.getRequestURL().toString());
if (log.isTraceEnabled()) {
log.trace("Request URL: " + requestUrl);
@@ -344,13 +343,34 @@ public class ServletAppUtils {
log.error("Failed to parse port from header: " + request.getHeader(HEADER_FORWARDED_PORT), e);
}
}
String finalScheme = uri.getScheme();
String finalHost = uri.getHost();
int finalPort = port;
boolean changed = false;
if (CommonUtils.isNotEmpty(forwardedScheme) && !forwardedScheme.equals(finalScheme)) {
finalScheme = forwardedScheme;
changed = true;
}
if (CommonUtils.isNotEmpty(forwardedHost) && !forwardedHost.equals(finalHost)) {
finalHost = forwardedHost;
changed = true;
}
if (DEFAULT_PORTS.contains(port)) {
finalPort = -1;
changed = true;
}
if (log.isTraceEnabled()) {
log.trace("Origin URI: " + origin);
}
if (changed) {
try {
origin = new URI(
uri.getScheme(),
finalScheme,
uri.getUserInfo(),
uri.getHost(),
-1,
finalHost,
finalPort,
uri.getPath(),
uri.getQuery(),
uri.getFragment()
@@ -359,9 +379,6 @@ public class ServletAppUtils {
log.error("Failed to create URI without port", e);
}
}
if (log.isTraceEnabled()) {
log.trace("Origin URI: " + origin);
}
return origin;
}
@@ -162,18 +162,24 @@ public class WebDataSourceUtils {
}
public static boolean disconnectDataSource(@NotNull WebSession webSession, @NotNull DBPDataSourceContainer dataSource) {
public static boolean disconnectDataSource(
@NotNull WebSession webSession,
@NotNull DBPDataSourceContainer dataSource,
boolean sendDisconnectEvent
) {
if (dataSource.isConnected()) {
try {
dataSource.disconnect(webSession.getProgressMonitor());
webSession.addSessionEvent(
new WSDataSourceDisconnectEvent(
dataSource.getProject().getId(),
dataSource.getId(),
webSession.getSessionId(),
webSession.getUserId()
)
);
if (sendDisconnectEvent) {
webSession.addSessionEvent(
new WSDataSourceDisconnectEvent(
dataSource.getProject().getId(),
dataSource.getId(),
webSession.getSessionId(),
webSession.getUserId()
)
);
}
return true;
} catch (DBException e) {
log.error("Error closing connection", e);
@@ -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.0.5.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 26.1.0.qualifier
Bundle-Release-Date: 20260601
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.0.5-SNAPSHOT</version>
<version>26.1.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.146.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.147.qualifier
Bundle-Release-Date: 20260601
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -16,6 +16,7 @@
<resource name="drivers/h2_v2"/>
<resource name="drivers/h2_v3"/>
<resource name="drivers/sqlite/xerial"/>
<resource name="drivers/libsql"/>
<resource name="drivers/mssql/new"/>
<resource name="drivers/trino"/>
<resource name="drivers/kyuubi"/>
@@ -38,6 +39,7 @@
<bundle id="drivers.h2_v2" label="H2 v2 drivers"/>
<bundle id="drivers.h2_v3" label="H2 v3 drivers"/>
<bundle id="drivers.sqlite.xerial" label="SQLite drivers"/>
<bundle id="drivers.libsql" label="LibSQL drivers"/>
<bundle id="drivers.mssql.new" label="SQL Server drivers"/>
<bundle id="drivers.trino" label="Trino drivers"/>
<bundle id="drivers.kyuubi" label="Apache Kyuubi drivers"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.146-SNAPSHOT</version>
<version>1.0.147-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.0.5.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 26.1.0.qualifier
Bundle-Release-Date: 20260601
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.0.5-SNAPSHOT</version>
<version>26.1.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.0.5.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 26.1.0.qualifier
Bundle-Release-Date: 20260601
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Require-Bundle: org.jkiss.dbeaver.model.sql;visibility:=reexport,
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>26.0.5-SNAPSHOT</version>
<version>26.1.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -678,7 +678,7 @@ public class WebServiceCore implements DBWServiceCore {
WebConnectionInfo connectionInfo = project.getWebConnectionInfo(connectionId);
DBPDataSourceContainer dataSourceContainer = connectionInfo.getDataSourceContainer();
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer);
WebDataSourceUtils.disconnectDataSource(webSession, dataSourceContainer, true);
return connectionInfo;
}
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,8 +116,7 @@ public class WebSQLUtils {
Map<String, Object> map = createMapOfType(WebSQLConstants.VALUE_TYPE_COLLECTION);
map.put("value", items);
return map;
} else if (value instanceof DBDComposite) {
DBDComposite composite = (DBDComposite)value;
} else if (value instanceof DBDComposite composite && composite.getAttributes().length > 0) {
Map<String, Object> struct = new LinkedHashMap<>();
for (DBSAttributeBase attr : composite.getAttributes()) {
struct.put(attr.getName(), makeWebCellValue(session, attr, composite.getAttributeValue(attr), dataFormat));
@@ -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.145.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.146.qualifier
Bundle-Release-Date: 20260601
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.145-SNAPSHOT</version>
<version>1.0.146-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -163,7 +163,7 @@ public class WebServiceAdmin implements DBWServiceAdmin {
if (userName.isEmpty()) {
throw new DBWebException("Empty user name");
}
String userId = userName.toLowerCase();
String userId = userName.trim().toLowerCase();
try {
GeneralUtils.validateResourceNameUnconditionally(userId);
} catch (DBException e) {
@@ -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.145.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.146.qualifier
Bundle-Release-Date: 20260601
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.145-SNAPSHOT</version>
<version>1.0.146-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.146.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.147.qualifier
Bundle-Release-Date: 20260601
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.146-SNAPSHOT</version>
<version>1.0.147-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -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.63.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.64.qualifier
Bundle-Release-Date: 20260601
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.63-SNAPSHOT</version>
<version>1.0.64-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.28.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.29.qualifier
Bundle-Release-Date: 20260601
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.28-SNAPSHOT</version>
<version>1.0.29-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2025 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -556,7 +556,7 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
log.debug("Found " + groupsByMemberAttribute.size() + " groups by member attribute");
result.addAll(groupsByMemberAttribute);
} catch (Exception e) {
log.error("Group not found", e);
log.error("Group not found. " + e.getMessage());
} finally {
try {
if (context != null) {
@@ -618,7 +618,7 @@ public class LdapAuthProvider implements SMAuthProviderExternal<SMSession>, SMBr
//add relative dn to base dn
result.add(next.getName());
} catch (Exception e) {
log.error("Failed fetch user group. Skipping...", e);
log.error("Failed fetch user group. " + e.getMessage());
}
}
} finally {
@@ -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.149.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.150.qualifier
Bundle-Release-Date: 20260601
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.149-SNAPSHOT</version>
<version>1.0.150-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.63.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.64.qualifier
Bundle-Release-Date: 20260601
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.63-SNAPSHOT</version>
<version>1.0.64-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.98.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.99.qualifier
Bundle-Release-Date: 20260601
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.98-SNAPSHOT</version>
<version>1.0.99-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.101.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.102.qualifier
Bundle-Release-Date: 20260601
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.101-SNAPSHOT</version>
<version>1.0.102-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.61.qualifier
Bundle-Release-Date: 20260518
Bundle-Version: 1.0.62.qualifier
Bundle-Release-Date: 20260601
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Fragment-Host: slf4j.api
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.61-SNAPSHOT</version>
<version>1.0.62-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
+32
View File
@@ -0,0 +1,32 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>drivers.libsql</artifactId>
<version>1.0.0</version>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>drivers</artifactId>
<version>1.0.0</version>
<relativePath>../</relativePath>
</parent>
<packaging>pom</packaging>
<properties>
<deps.output.dir>libsql</deps.output.dir>
</properties>
<dependencies>
<dependency>
<groupId>com.dbeaver.jdbc</groupId>
<artifactId>com.dbeaver.jdbc.driver.libsql</artifactId>
<version>RELEASE</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+1
View File
@@ -26,6 +26,7 @@
<module>h2_v3</module>
<module>jaybird</module>
<module>kyuubi</module>
<module>libsql</module>
<module>mysql</module>
<module>mariadb</module>
<module>oracle</module>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.169.qualifier"
version="1.0.170.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.169-SNAPSHOT</version>
<version>1.0.170-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="26.0.5.qualifier"
version="26.1.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.0.5-SNAPSHOT</version>
<version>26.1.0-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="26.0.5.qualifier"
version="26.1.0.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>26.0.5-SNAPSHOT</version>
<version>26.1.0-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.99.qualifier"
version="1.0.100.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.99-SNAPSHOT</version>
<version>1.0.100-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.0.5.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="26.1.0.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>26.0.5-SNAPSHOT</version>
<version>26.1.0-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
@@ -1,5 +1,13 @@
:global(.rdg-cell) .editor {
appearance: none;
resize: none;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
box-sizing: border-box;
min-height: initial;
@@ -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.
@@ -11,9 +11,22 @@ import classes from './TextEditor.module.css';
import { DataGridCellContext } from '../DataGridCellContext.js';
import { useGridReactiveValue } from '../useGridReactiveValue.js';
function autoFocusAndSelect(input: HTMLInputElement | null) {
input?.focus();
input?.select();
function autoFocusAndSelect(input: HTMLTextAreaElement | null) {
if (!input) {
return;
}
input.focus();
input.select();
input.addEventListener(
'beforeinput',
e => {
if (e.inputType === 'insertLineBreak') {
e.preventDefault();
}
},
{ once: true },
);
}
export interface IProps {
@@ -28,9 +41,11 @@ export function TextEditor({ rowIdx, colIdx, onClose }: IProps) {
const value = useGridReactiveValue(cellContext?.cellText, rowIdx, colIdx) ?? '';
return (
<input
className={classes['editor']}
<textarea
ref={autoFocusAndSelect}
className={classes['editor']}
rows={1}
wrap="off"
value={value}
onChange={event => cellContext?.onCellChange?.(rowIdx, colIdx, event.target.value)}
onBlur={() => onClose()}
@@ -17,6 +17,7 @@
outline: 0;
padding-block: var(--dbv-kit-menu-popover-padding-block);
padding-inline: var(--dbv-kit-menu-popover-padding-inline);
z-index: 1000;
@media (prefers-reduced-motion: no-preference) {
opacity: 0;
@@ -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.
@@ -78,6 +78,8 @@ export interface SelectFieldProps<T, ItemType = SelectItem<T>> {
store?: SelectProviderProps['store'];
portal?: boolean;
autoFocusItemsOnShow?: boolean;
'aria-labelledby'?: string;
@@ -106,6 +108,7 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
disabled,
required,
className,
portal = false,
selectedRender,
arrowIcon,
store,
@@ -167,7 +170,7 @@ export function SelectField<T, ItemType extends {} = SelectItem<T>>({
</Select>
{description && <span className="dbv-kit-select__description">{description}</span>}
<SelectPopover autoFocusOnShow={autoFocusItemsOnShow} gutter={4} unmountOnHide>
<SelectPopover autoFocusOnShow={autoFocusItemsOnShow} portal={portal} gutter={4} unmountOnHide>
{items.length === 0 ? (
<div className="dbv-kit-select__empty">{noItemsPlaceholder}</div>
) : (
@@ -55,12 +55,21 @@ export const CommonDialogWrapper = observer<CommonDialogWrapperProps, HTMLDivEle
}
}
// We need to specify persistent notifications for the dialog to make them accessible when the dialog is open.
function getDefaultPersistentElements(): Element[] {
const dialogs = document.querySelectorAll('[data-dialog]');
const persistentNotifications = document.querySelectorAll('[data-dialog-persistent-notification]');
return [...dialogs, ...persistentNotifications];
}
return (
<Dialog
ref={ref}
aria-label={ariaLabel}
open={context.visible}
data-size={size}
getPersistentElements={getDefaultPersistentElements}
className={s(computedStyles, { dialog: true, fixedSize, fixedWidth, freeHeight }, 'tw:shadow-xlarge', className)}
autoFocusOnShow={autoFocusOnShow}
autoFocusOnHide={autoFocusOnHide}
@@ -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.
@@ -39,6 +39,7 @@ export type SelectBaseProps<TKey, TValue> = Omit<
onSwitch?: (state: boolean) => void;
inline?: boolean;
children?: string;
portal?: boolean;
};
type ControlledProps<TKey, TValue> = SelectBaseProps<TKey, TValue> & {
@@ -66,6 +67,7 @@ export const Select: SelectType = observer(function Select({
name,
state,
items,
portal = false,
loading,
children,
title,
@@ -180,6 +182,7 @@ export const Select: SelectType = observer(function Select({
)}
<SelectField
{...rest}
portal={portal}
items={items}
value={value}
id={inputId}
@@ -15,5 +15,5 @@
flex-direction: column-reverse;
flex-wrap: nowrap;
justify-content: flex-end;
z-index: 1000; /* modal dialogs is 999, but more correct way is place notifications after dialogs in the dom */
z-index: 1100; /* Dialog has z-index: 1000 so to show it above backdrop we need to keep it higher */
}
@@ -22,7 +22,7 @@ export const Notifications = observer(function Notifications() {
return (
<Portal>
<div className={s(style, { notifications: true })} data-dialog-persistent-element>
<div className={s(style, { notifications: true })} data-dialog-persistent-element data-dialog-persistent-notification>
{notificationService.visibleNotifications.map(notification => (
<NotificationsItem key={notification.id} notification={notification} />
))}
@@ -9,6 +9,10 @@ mutation getSqlExecutionPlanResult($taskId: ID!) {
type
condition
description
cost
rowCount
duration
percent
properties {
id
required
@@ -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.
@@ -21,6 +21,7 @@ export interface IContextMenuNewProps extends IContextMenuProps {
ref?: React.ForwardedRef<HTMLButtonElement>;
shift?: number;
gutter?: number;
autoFocusOnShow?: boolean;
}
export const ContextMenu = observer<IContextMenuNewProps>(function ContextMenuInner({
@@ -33,6 +34,7 @@ export const ContextMenu = observer<IContextMenuNewProps>(function ContextMenuIn
rtl,
shift,
gutter,
autoFocusOnShow,
...rest
}) {
const translate = useTranslate();
@@ -40,6 +42,7 @@ export const ContextMenu = observer<IContextMenuNewProps>(function ContextMenuIn
const menu = useMenuStore({ placement, rtl: rtl || parent?.rtl, open: visible });
const isRtl = useStoreState(menu, 'rtl');
const isMenuOpen = useStoreState(menu, 'open');
const isAutoFocus = useStoreState(menu, 'autoFocusOnShow');
const handler = menuData.handler;
@@ -77,6 +80,12 @@ export const ContextMenu = observer<IContextMenuNewProps>(function ContextMenuIn
const menuContext = useMemo<IMenuContext>(() => ({ menu: menuData, rtl: isRtl }), [menuData, isRtl]);
useLayoutEffect(() => {
if (autoFocusOnShow && !isAutoFocus) {
menu.setAutoFocusOnShow(true);
}
}, [autoFocusOnShow, isAutoFocus, menu]);
if (handler?.isHidden?.(menuData.context)) {
return null;
}
@@ -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.
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -28,7 +28,7 @@
"fast-deep-equal": "^3",
"md5": "^2",
"mobx": "^6",
"modern-screenshot": "^4",
"modern-screenshot": "4.6.8",
"tslib": "^2",
"underscore": "^1",
"uuid": "^14",
@@ -0,0 +1,252 @@
/*
* 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 { describe, expect, it, vi } from 'vitest';
import { isBindingPressed, isHotkeyModifier, mapKey, parseHotkey, parseKeysHookInput } from './parseHotkey.js';
import type { IKeyBinding } from '../Action/KeyBinding/IKeyBinding.js';
vi.mock('../Action/KeyBinding/getCommonAndOSSpecificKeys.js', () => ({
getCommonAndOSSpecificKeys: (binding: IKeyBinding) => (binding.keys ? [binding.keys] : []),
}));
// ---------------------------------------------------------------------------
// mapKey
// ---------------------------------------------------------------------------
describe('mapKey', () => {
it('maps aliased keys', () => {
expect(mapKey('esc')).toBe('escape');
expect(mapKey('return')).toBe('enter');
expect(mapKey(' ')).toBe('space');
expect(mapKey('.')).toBe('period');
expect(mapKey(',')).toBe('comma');
expect(mapKey('-')).toBe('slash');
expect(mapKey('`')).toBe('backquote');
expect(mapKey('#')).toBe('backslash');
expect(mapKey('+')).toBe('bracketright');
});
it('maps physical key codes for modifiers', () => {
expect(mapKey('ShiftLeft')).toBe('shift');
expect(mapKey('ShiftRight')).toBe('shift');
expect(mapKey('AltLeft')).toBe('alt');
expect(mapKey('AltRight')).toBe('alt');
expect(mapKey('MetaLeft')).toBe('meta');
expect(mapKey('MetaRight')).toBe('meta');
expect(mapKey('OSLeft')).toBe('meta');
expect(mapKey('OSRight')).toBe('meta');
expect(mapKey('ControlLeft')).toBe('ctrl');
expect(mapKey('ControlRight')).toBe('ctrl');
});
it('strips key/digit/numpad/arrow prefixes', () => {
expect(mapKey('KeyA')).toBe('a');
expect(mapKey('Digit1')).toBe('1');
expect(mapKey('Numpad0')).toBe('0');
expect(mapKey('ArrowUp')).toBe('up');
});
it('lowercases and trims unknown keys', () => {
expect(mapKey(' Enter ')).toBe('enter');
expect(mapKey('A')).toBe('a');
});
});
// ---------------------------------------------------------------------------
// isHotkeyModifier
// ---------------------------------------------------------------------------
describe('isHotkeyModifier', () => {
it('returns true for modifier keywords', () => {
expect(isHotkeyModifier('shift')).toBe(true);
expect(isHotkeyModifier('alt')).toBe(true);
expect(isHotkeyModifier('meta')).toBe(true);
expect(isHotkeyModifier('mod')).toBe(true);
expect(isHotkeyModifier('ctrl')).toBe(true);
});
it('returns false for regular keys', () => {
expect(isHotkeyModifier('a')).toBe(false);
expect(isHotkeyModifier('enter')).toBe(false);
expect(isHotkeyModifier('escape')).toBe(false);
});
});
// ---------------------------------------------------------------------------
// parseKeysHookInput
// ---------------------------------------------------------------------------
describe('parseKeysHookInput', () => {
it('splits by comma by default', () => {
expect(parseKeysHookInput('ctrl+a,ctrl+b')).toEqual(['ctrl+a', 'ctrl+b']);
});
it('uses a custom split key', () => {
expect(parseKeysHookInput('ctrl+a|ctrl+b', '|')).toEqual(['ctrl+a', 'ctrl+b']);
});
it('returns single element array when no delimiter present', () => {
expect(parseKeysHookInput('ctrl+a')).toEqual(['ctrl+a']);
});
});
// ---------------------------------------------------------------------------
// parseHotkey
// ---------------------------------------------------------------------------
describe('parseHotkey', () => {
it('parses a simple key with no modifiers', () => {
const result = parseHotkey('a');
expect(result).toMatchObject({ alt: false, ctrl: false, shift: false, meta: false, mod: false, keys: ['a'] });
});
it('parses ctrl modifier', () => {
const result = parseHotkey('ctrl+a');
expect(result).toMatchObject({ ctrl: true, alt: false, shift: false, meta: false, mod: false, keys: ['a'] });
});
it('parses mod modifier', () => {
const result = parseHotkey('mod+s');
expect(result).toMatchObject({ mod: true, ctrl: false, meta: false, keys: ['s'] });
});
it('parses shift+alt+key combination', () => {
const result = parseHotkey('shift+alt+z');
expect(result).toMatchObject({ shift: true, alt: true, ctrl: false, meta: false, mod: false, keys: ['z'] });
});
it('parses meta modifier', () => {
const result = parseHotkey('meta+k');
expect(result).toMatchObject({ meta: true, ctrl: false, keys: ['k'] });
});
it('handles control as alias for ctrl', () => {
const result = parseHotkey('control+v');
expect(result.ctrl).toBe(true);
expect(result.keys).toContain('v');
});
it('maps aliased key in hotkey string', () => {
const result = parseHotkey('ctrl+esc');
expect(result.ctrl).toBe(true);
expect(result.keys).toContain('escape');
});
it('supports custom combination key', () => {
const result = parseHotkey('ctrl-a', '-');
expect(result.ctrl).toBe(true);
expect(result.keys).toContain('a');
});
it('sets isSequence and useKey to false', () => {
const result = parseHotkey('ctrl+a');
expect(result.isSequence).toBe(false);
expect(result.useKey).toBe(false);
});
it('does not include modifiers in keys array', () => {
const result = parseHotkey('shift+ctrl+alt+meta+x');
expect(result.keys).toEqual(['x']);
});
});
// ---------------------------------------------------------------------------
// isBindingPressed
// ---------------------------------------------------------------------------
function makeEvent(overrides: Partial<KeyboardEvent>): KeyboardEvent {
return {
key: 'a',
ctrlKey: false,
metaKey: false,
shiftKey: false,
altKey: false,
...overrides,
} as unknown as KeyboardEvent;
}
describe('isBindingPressed', () => {
it('returns true when ctrl+a binding matches ctrlKey+a event', () => {
const binding: IKeyBinding = { id: 'test', keys: 'ctrl+a' };
const event = makeEvent({ ctrlKey: true, key: 'a' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
it('returns false when required ctrl modifier is missing', () => {
const binding: IKeyBinding = { id: 'test', keys: 'ctrl+a' };
const event = makeEvent({ key: 'a' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns true when mod binding matches ctrlKey event', () => {
const binding: IKeyBinding = { id: 'test', keys: 'mod+s' };
const event = makeEvent({ ctrlKey: true, key: 's' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
it('returns true when mod binding matches metaKey event', () => {
const binding: IKeyBinding = { id: 'test', keys: 'mod+s' };
const event = makeEvent({ metaKey: true, key: 's' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
it('returns false when shift modifier is missing', () => {
const binding: IKeyBinding = { id: 'test', keys: 'shift+a' };
const event = makeEvent({ key: 'a' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns true for shift+alt+key when both modifiers present', () => {
const binding: IKeyBinding = { id: 'test', keys: 'shift+alt+x' };
const event = makeEvent({ shiftKey: true, altKey: true, key: 'x' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
it('returns false when key does not match', () => {
const binding: IKeyBinding = { id: 'test', keys: 'ctrl+a' };
const event = makeEvent({ ctrlKey: true, key: 'b' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns false when binding has no keys', () => {
const binding: IKeyBinding = { id: 'test' };
const event = makeEvent({ key: 'a' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('maps event key through mapKey before comparing', () => {
const binding: IKeyBinding = { id: 'test', keys: 'escape' };
const event = makeEvent({ key: 'Escape' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
it('returns false when an extra shift is pressed but binding is mod+/', () => {
const binding: IKeyBinding = { id: 'test', keys: 'mod+/' };
const event = makeEvent({ ctrlKey: true, shiftKey: true, key: '/' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns false when an extra alt is pressed but binding is ctrl+s', () => {
const binding: IKeyBinding = { id: 'test', keys: 'ctrl+s' };
const event = makeEvent({ ctrlKey: true, altKey: true, key: 's' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns false when an extra shift and alt are pressed but binding is mod+z', () => {
const binding: IKeyBinding = { id: 'test', keys: 'mod+z' };
const event = makeEvent({ ctrlKey: true, shiftKey: true, altKey: true, key: 'z' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns false when ctrl is pressed but binding is shift+a (extra ctrl)', () => {
const binding: IKeyBinding = { id: 'test', keys: 'shift+a' };
const event = makeEvent({ shiftKey: true, ctrlKey: true, key: 'a' });
expect(isBindingPressed(event as any, binding)).toBe(false);
});
it('returns true when exact modifiers match and no extra modifiers are pressed (mod+/)', () => {
const binding: IKeyBinding = { id: 'test', keys: 'mod+/' };
const event = makeEvent({ ctrlKey: true, key: '/' });
expect(isBindingPressed(event as any, binding)).toBe(true);
});
});
@@ -1,12 +1,16 @@
/*
* 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 type { Hotkey } from '@cloudbeaver/core-blocks';
import type { KeyboardEvent } from 'react';
import { getCommonAndOSSpecificKeys } from '../Action/KeyBinding/getCommonAndOSSpecificKeys.js';
import type { IKeyBinding } from '../Action/KeyBinding/IKeyBinding.js';
const reservedModifierKeywords = ['shift', 'alt', 'meta', 'mod', 'ctrl'];
@@ -47,6 +51,36 @@ export function parseKeysHookInput(keys: string, splitKey = ','): string[] {
return keys.split(splitKey);
}
export function isBindingPressed<T extends HTMLElement>(event: KeyboardEvent<T>, binding: IKeyBinding, combinationKey = '+'): boolean {
const keys = getCommonAndOSSpecificKeys(binding);
return keys.some(hotkey => {
const parsed = parseHotkey(hotkey, combinationKey);
const mod = event.ctrlKey || event.metaKey;
if (!!parsed.shift !== event.shiftKey) {
return false;
}
if (!!parsed.alt !== event.altKey) {
return false;
}
if (parsed.mod) {
if (!mod) {
return false;
}
} else {
if (event.ctrlKey !== !!parsed.ctrl) {
return false;
}
if (event.metaKey !== !!parsed.meta) {
return false;
}
}
return parsed.keys?.some(k => mapKey(event.key) === k) ?? false;
});
}
export function parseHotkey(hotkey: string, combinationKey = '+'): Hotkey {
const keys = hotkey
.toLocaleLowerCase()
+1
View File
@@ -86,6 +86,7 @@ export * from './View/CaptureViewLazy.js';
export * from './View/CaptureViewContext.js';
export * from './View/IActiveView.js';
export * from './View/IView.js';
export * from './View/parseHotkey.js';
export * from './View/useActiveView.js';
export * from './View/useCaptureViewContext.js';
export * from './View/View.js';
@@ -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.
@@ -16,6 +16,7 @@ import { ADMINISTRATION_TABLE_DEFAULT_ROW_HEIGHT, AdministrationTableStyles } fr
import { DataGrid, useCreateGridReactiveValue, TableRowSelect } from '@cloudbeaver/plugin-data-grid';
import { TeamsTableOptionsPanelService } from './TeamsTableOptionsPanelService.js';
import { Command } from '@dbeaver/ui-kit';
interface Props {
teams: TeamInfo[];
@@ -54,13 +55,15 @@ export const TeamsTable = observer<Props>(function TeamsTable({ teams }) {
if (column.key === ID_COLUMN.key) {
return (
<div
<Command
render={<div />}
tabIndex={0}
title={row.teamId}
className="tw:flex tw:cursor-pointer tw:items-center tw:gap-2"
className="tw:flex tw:cursor-pointer tw:items-center tw:gap-2 tw:outline-none"
onClick={() => teamsTableOptionsPanelService.open(row.teamId)}
>
<Link truncate>{row.teamId}</Link>
</div>
</Command>
);
}
@@ -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.
@@ -20,6 +20,7 @@ import { DataGrid, useCreateGridReactiveValue } from '@cloudbeaver/plugin-data-g
import { UsersTableOptionsPanelService } from './UsersTableOptionsPanelService.js';
import { UsersAdministrationService } from '../UsersAdministrationService.js';
import { Command } from '@dbeaver/ui-kit';
interface Props {
users: AdminUserInfoFragment[];
@@ -84,13 +85,15 @@ export const UsersTable = observer<Props>(function UsersTable({ users, isManagea
if (column.key === ID_COLUMN.key) {
return (
<div
<Command
render={<div />}
tabIndex={0}
title={row.userId}
className="tw:flex tw:cursor-pointer tw:items-center tw:gap-2"
className="tw:flex tw:cursor-pointer tw:items-center tw:gap-2 tw:outline-none"
onClick={() => usersTableOptionsPanelService.open(row.userId)}
>
<Link truncate>{row.userId}</Link>
</div>
</Command>
);
}
@@ -36,6 +36,7 @@
"@codemirror/lang-sql": "^6",
"@codemirror/lang-xml": "^6",
"@codemirror/language": "^6",
"@codemirror/lint": "^6",
"@codemirror/merge": "^6",
"@codemirror/search": "^6",
"@codemirror/state": "^6",
@@ -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.
@@ -26,9 +26,11 @@ export * from '@codemirror/state';
export * from '@codemirror/autocomplete';
export * from './highlightNewLine.js';
export { history } from '@codemirror/commands';
export { html as HTML_EDITOR } from '@codemirror/lang-html';
export { javascript as JAVASCRIPT_EDITOR } from '@codemirror/lang-javascript';
export { json as JSON_EDITOR } from '@codemirror/lang-json';
export { json as JSON_EDITOR, jsonParseLinter } from '@codemirror/lang-json';
export { linter, lintGutter } from '@codemirror/lint';
export { sql as SQL_EDITOR, SQLDialect } from '@codemirror/lang-sql';
export { xml as XML_EDITOR } from '@codemirror/lang-xml';
@@ -386,18 +386,25 @@ export class ConnectionFormOptionsPart extends FormPart<IConnectionFormOptionsSt
const authPropertiesInfo = this.connectionKey ? await this.connectionInfoAuthPropertiesResource.load(this.connectionKey) : undefined;
const properties = await this.getConnectionAuthModelProperties(this.state.authModelId, authPropertiesInfo);
const passwordProperty = properties.find(property => property.features.includes('password'));
const isPasswordEmpty =
passwordProperty &&
(this.state.credentials?.[passwordProperty.id!] === getObjectPropertyDefaultValue(passwordProperty) ||
!this.state.credentials?.[passwordProperty.id!]);
const passwordProperties = properties.filter(property => property.features.includes('password'));
if (isCredentialsChanged(properties, this.state.credentials!)) {
this.state.credentials = prepareDynamicProperties(properties, toJS(this.state.credentials!));
}
if (isPasswordEmpty) {
delete this.state.credentials?.[passwordProperty.id!];
if (passwordProperties.length > 0) {
for (const passwordProperty of passwordProperties) {
if (!passwordProperty.id) {
continue;
}
if (
this.state.credentials?.[passwordProperty.id] === getObjectPropertyDefaultValue(passwordProperty) ||
(!passwordProperty.features.includes('file') && !this.state.credentials?.[passwordProperty.id])
) {
delete this.state.credentials?.[passwordProperty.id];
}
}
}
}
@@ -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.
@@ -40,7 +40,6 @@ export class ConnectionOriginInfoTabService extends Bootstrap {
const key = optionsPart?.connectionKey;
if (!key) {
console.warn('Cannot load connection origin info: connection key is not defined');
return [];
}
@@ -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.
@@ -25,6 +25,7 @@
"dependencies": {
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-browser": "workspace:*",
"@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-data-context": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-dialogs": "workspace:*",
@@ -13,7 +13,7 @@ import { getComputed, useHover, useMergeRefs, useObjectRef, useObservableRef } f
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
import { clsx } from '@dbeaver/ui-kit';
import { type IDataGridCellRenderer, type ICellPosition } from '@cloudbeaver/plugin-data-grid';
import { DatabaseEditChangeType, type IGridDataKey, type IGridRowKey } from '@cloudbeaver/plugin-data-viewer';
import { DatabaseEditChangeType, KEY_BINDING_OPEN_CELL_CONTEXT_MENU, type IGridDataKey, type IGridRowKey } from '@cloudbeaver/plugin-data-viewer';
import { isObjectsEqual } from '@cloudbeaver/core-utils';
import { ColumnDnDContext } from '../ColumnDnDContext.js';
@@ -23,6 +23,7 @@ import { TableDataContext, type IColumnInfo } from '../TableDataContext.js';
import { CellContext } from './CellContext.js';
import { useDataEditorDnDBox } from '../useDataEditorDnDBox.js';
import { getDropSide } from '../getDropSide.js';
import { isBindingPressed } from '@cloudbeaver/core-view';
interface Props {
rowIdx: number;
@@ -144,6 +145,13 @@ export const CellRenderer = observer<Props>(function CellRenderer({ rowIdx, colI
this.selectionContext.clearSelection();
}
},
keyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (isBindingPressed(event, KEY_BINDING_OPEN_CELL_CONTEXT_MENU)) {
event.preventDefault();
event.stopPropagation();
this.cellContext.setMenuVisibility(!this.cellContext.isMenuVisible);
}
},
openContextMenu(event: React.MouseEvent<HTMLDivElement>) {
if (EventContext.has(event, EventStopPropagationFlag)) {
return;
@@ -177,7 +185,7 @@ export const CellRenderer = observer<Props>(function CellRenderer({ rowIdx, colI
dataGridContext,
cellContext,
},
['mouseUp', 'openContextMenu'],
['keyDown', 'mouseUp', 'openContextMenu'],
);
const formatting = getComputed(
@@ -195,6 +203,7 @@ export const CellRenderer = observer<Props>(function CellRenderer({ rowIdx, colI
style: formatting || undefined,
'data-row-index': rowIdx,
'data-column-index': colIdx,
onKeyDown: state.keyDown,
onMouseUp: state.mouseUp,
onContextMenu: state.openContextMenu,
})}
@@ -27,7 +27,6 @@ import {
DATA_CONTEXT_DV_PRESENTATION,
type DatabaseDataSelectActionsData,
DatabaseEditChangeType,
DatabaseSelectAction,
DataViewerPresentationType,
type IDatabaseDataModel,
type IDataPresentationProps,
@@ -186,7 +185,7 @@ export const DataGridTable = observer<IDataPresentationProps>(function DataGridT
},
}));
const gridSelectedCellCopy = useGridSelectedCellsCopy(tableData, selectionAction as unknown as DatabaseSelectAction, gridSelectionContext);
const gridSelectedCellCopy = useGridSelectedCellsCopy(tableData, selectionAction, gridSelectionContext);
const gridSelectedCellPaste = useGridSelectedCellsPaste(tableData, selectionAction as unknown as ResultSetSelectAction);
const { onMouseDownHandler, onMouseMoveHandler } = useGridDragging({
onDragStart: startPosition => {
@@ -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.
@@ -39,7 +39,7 @@ export const TextFormatter = observer<ICellFormatterProps>(function TextFormatte
const classes = s(style, { textFormatter: true });
return (
<div title={displayValue} className={classes}>
<div title={textValue} className={classes}>
{isValidUrl(textValue) && (
<a href={textValue} target="_blank" rel="noreferrer" draggable={false} className={s(style, { a: true })}>
<IconOrImage icon="external-link" viewBox="0 0 24 24" className={s(style, { icon: true })} />
@@ -75,7 +75,13 @@ export const CellMenu = observer<Props>(function CellMenu({ model, actions, spre
return (
<SContext registry={registry}>
<div className={s(style, { container: true })} onMouseUp={markStopPropagation} onDoubleClick={stopPropagation}>
<ContextMenu className={s(style, { contextMenu: true })} menu={menu} visible={cellContext.isMenuVisible} onVisibleSwitch={onStateSwitch}>
<ContextMenu
className={s(style, { contextMenu: true })}
menu={menu}
visible={cellContext.isMenuVisible}
autoFocusOnShow
onVisibleSwitch={onStateSwitch}
>
<Icon className={s(style, { icon: true })} name="snack" viewBox="0 0 16 10" />
</ContextMenu>
</div>
@@ -9,8 +9,9 @@ import { SqlRowIdentifierState, type SqlResultColumn } from '@cloudbeaver/core-s
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { IconOrImage, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer';
import { IconOrImage, s, useResource, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { DatabaseDataFeature, isResultSetDataSource } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContext } from '../DataGridContext.js';
import { TableDataContext } from '../TableDataContext.js';
@@ -20,16 +21,27 @@ import styles from './TableStatusIndicator.module.css';
export const TableStatusIndicator = observer(function TableStatusIndicator() {
const dataGridContext = useContext(DataGridContext);
const tableDataContext = useContext(TableDataContext);
const readOnlyConnection = dataGridContext.model.isReadonly(dataGridContext.resultIndex);
const translate = useTranslate();
const source = dataGridContext.model.source;
const resultSetSource = isResultSetDataSource(source) ? source : null;
/* We do NOT use `model.isReadonly()` here —
that method aggregates several unrelated reasons
and loses information about WHY editing isn't allowed. */
const contextInfo = resultSetSource?.executionContext?.context;
const connectionKey = contextInfo ? createConnectionParam(contextInfo.projectId, contextInfo.connectionId) : null;
const connectionInfoLoader = useResource(TableStatusIndicator, ConnectionInfoResource, connectionKey);
const readOnlyConnection = connectionInfoLoader.data?.readOnly ?? false;
const readOnlyPresentation = source.hasFeature(DatabaseDataFeature.Grouping);
const style = useS(styles);
if (!tableDataContext || !dataGridContext) {
return null;
}
const source = dataGridContext.model.source;
const resultSetSource = isResultSetDataSource(source) ? source : null;
const rowIdentifierInfo = resultSetSource?.getRowIdentifierInfo(dataGridContext.resultIndex);
const hasRowIdentifier = resultSetSource?.hasElementIdentifier(dataGridContext.resultIndex);
@@ -43,7 +55,10 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
const isPrimaryKey = rowIdentifierInfo?.state === SqlRowIdentifierState.PrimaryKey;
const tooltipParts: string[] = [];
if (readOnlyConnection) {
// Presentation-level read-only takes precedence over connection-level read-only.
if (readOnlyPresentation) {
tooltipParts.push(translate('data_grid_table_readonly_presentation_tooltip'));
} else if (readOnlyConnection) {
tooltipParts.push(translate('data_grid_table_readonly_connection_tooltip'));
}
@@ -59,6 +74,15 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
tooltipParts.push(translate('data_grid_table_virtual_key_tooltip'));
}
const showLockIcon = readOnlyConnection || readOnlyPresentation;
// Hide the entire indicator when there's nothing meaningful to display (Session Manager)
const hasInfo = showLockIcon || !!readOnlyStatus || hasRowIdentifier || isVirtualKey || isPrimaryKey;
if (!hasInfo) {
return null;
}
const tooltip = tooltipParts.join('\n');
return (
@@ -66,7 +90,7 @@ export const TableStatusIndicator = observer(function TableStatusIndicator() {
title={tooltip}
className="tw:absolute tw:top-1/2 tw:left-1 tw:-translate-y-1/2 tw:z-1 tw:pointer-events-auto tw:flex tw:items-center tw:gap-1 tw:cursor-help"
>
{readOnlyConnection && <IconOrImage icon="/icons/lock.png" className="tw:w-2.5 tw:cursor-help" />}
{showLockIcon && <IconOrImage icon="/icons/lock.png" className="tw:w-2.5 tw:cursor-help" />}
<div
className={s(
style,
@@ -38,6 +38,8 @@ export interface ITableData {
hasDescription: boolean;
columns: Array<IColumnInfo>;
columnKeys: IGridColumnKey[];
visualColumnKeys: IGridColumnKey[];
visualColumns: Array<IColumnInfo>;
rows: IGridRowKey[];
gridDiv: HTMLDivElement | null;
inBounds: (position: IGridDataKey) => boolean;
@@ -48,6 +50,7 @@ export interface ITableData {
getColumnInfo: (key: IGridColumnKey) => SqlResultColumn | undefined;
getColumnsInRange: (startIndex: number, endIndex: number) => Array<IColumnInfo>;
getColumnIndexFromColumnKey: (column: IGridColumnKey) => number;
getVisualColumnIndexFromColumnKey: (column: IGridColumnKey) => number;
getRowIndexFromKey: (row: IGridRowKey) => number;
getEditionState: (key: IGridDataKey) => DatabaseEditChangeType | null;
isCellEdited: (key: IGridDataKey) => boolean;
@@ -12,13 +12,12 @@ import { useService } from '@cloudbeaver/core-di';
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
import { copyToClipboard } from '@cloudbeaver/core-utils';
import {
DatabaseSelectAction,
DataViewerService,
type IGridColumnKey,
type IGridDataKey,
GridDataKeysUtils,
ResultSetSelectAction,
useDataViewerCopyHandler,
GridSelectAction,
} from '@cloudbeaver/plugin-data-viewer';
import type { IDataGridSelectionContext } from './DataGridSelection/DataGridSelectionContext.js';
@@ -47,7 +46,7 @@ function getSelectedCellsValue(tableData: ITableData, selectedCells: Map<string,
const rowsValues: string[] = [];
for (const rowSelection of orderedSelectedCells.values()) {
const rowCellsValues: string[] = [];
for (const column of tableData.view.columnKeys) {
for (const column of tableData.view.visualColumnKeys) {
if (!selectedColumns.some(columnKey => GridDataKeysUtils.isEqual(columnKey, column))) {
continue;
}
@@ -68,7 +67,7 @@ function getSelectedCellsValue(tableData: ITableData, selectedCells: Map<string,
export function useGridSelectedCellsCopy(
tableData: ITableData,
selectAction: DatabaseSelectAction | undefined,
selectAction: GridSelectAction | undefined,
selectionContext: IDataGridSelectionContext,
) {
const dataViewerService = useService(DataViewerService);
@@ -80,7 +79,7 @@ export function useGridSelectedCellsCopy(
const activeElement = document.activeElement as HTMLElement | null;
const isEditing = activeElement?.matches('input, textarea, [contenteditable="true"]');
if(isEditing) {
if (isEditing) {
return;
}
@@ -93,11 +92,8 @@ export function useGridSelectedCellsCopy(
EventContext.set(event, EventStopPropagationFlag);
if (dataViewerService.canCopyData) {
if (!(props.selectAction instanceof ResultSetSelectAction)) {
throw new Error('Copying data is not supported');
}
const focusedElement = props.selectAction?.getFocusedElement();
let value: string | null = null;
if (Array.from(props.selectionContext.selectedCells.keys()).length > 0) {
@@ -60,6 +60,16 @@ export function useGridSelectedCellsPaste(
return;
}
if (selectedCells.length === 1) {
const [target] = selectedCells;
if (target && tableData.isCellEditable(target)) {
tableData.editor.set(target, clipboardText);
}
return;
}
const matrix = parseTSV(clipboardText);
const rows = matrix.length;
const cols = matrix[0]?.length ?? 0;
@@ -69,7 +79,7 @@ export function useGridSelectedCellsPaste(
}
const getRowIdx = (key: IGridDataKey) => tableData.getRowIndexFromKey(key.row);
const getColIdx = (key: IGridDataKey) => tableData.getColumnIndexFromColumnKey(key.column);
const getColIdx = (key: IGridDataKey) => tableData.getVisualColumnIndexFromColumnKey(key.column);
// row-major order (top-to-bottom, left-to-right)
const targets = selectedCells.slice().sort((a, b) => {
@@ -62,20 +62,17 @@ export function useTableData(
get columnKeys(): IGridColumnKey[] {
return this.view.columnKeys;
},
get visualColumnKeys(): IGridColumnKey[] {
return this.view.visualColumnKeys;
},
get rows(): IGridRowKey[] {
return this.view.rowKeys;
},
get columns() {
if (this.columnKeys.length === 0) {
return [];
}
const columns: Array<IColumnInfo> = this.columnKeys.map<IColumnInfo>(col => ({
key: col,
}));
columns.unshift({ key: null });
return columns;
return getColumns(this.columnKeys);
},
get visualColumns() {
return getColumns(this.visualColumnKeys);
},
get hasDescription(): boolean {
if (!this.dataGridSettingsService.description) {
@@ -103,7 +100,10 @@ export function useTableData(
return this.view.getCellHolder(key) as IDatabaseValueHolder<IGridDataKey, IResultSetValue>;
},
getColumnIndexFromColumnKey(columnKey) {
return this.columns.findIndex(column => column.key !== null && GridDataKeysUtils.isEqual(columnKey, column.key));
return getColumnIndex(columnKey, this.columns);
},
getVisualColumnIndexFromColumnKey(columnKey) {
return getColumnIndex(columnKey, this.visualColumns);
},
getRowIndexFromKey(rowKey) {
return this.rows.findIndex(row => GridDataKeysUtils.isEqual(rowKey, row));
@@ -171,6 +171,8 @@ export function useTableData(
columns: computed,
rows: computed,
columnKeys: computed,
visualColumns: computed,
visualColumnKeys: computed,
hasDescription: computed,
formatting: observable.ref,
format: observable.ref,
@@ -192,3 +194,21 @@ export function useTableData(
},
);
}
function getColumns(columnKeys: IGridColumnKey[]): IColumnInfo[] {
if (columnKeys.length === 0) {
return [];
}
const columns: Array<IColumnInfo> = columnKeys.map<IColumnInfo>(col => ({
key: col,
}));
columns.unshift({ key: null });
return columns;
}
function getColumnIndex(columnKey: IGridColumnKey, columns: IColumnInfo[]): number {
return columns.findIndex(column => column.key !== null && GridDataKeysUtils.isEqual(column.key, columnKey));
}
@@ -25,6 +25,7 @@ export default [
['data_grid_table_context_menu_filter_dialog_title', 'Wert bearbeiten'],
['data_grid_table_index_column_tooltip', 'Wählen ganze Tabelle aus'],
['data_grid_table_readonly_connection_tooltip', 'Schreibgeschützte Verbindung'],
['data_grid_table_readonly_presentation_tooltip', 'Die aktive Darstellung unterstützt keine Datenbearbeitung'],
['data_grid_table_no_key_found_tooltip', 'Kein eindeutiger Schlüssel gefunden. Datenänderung nicht möglich.'],
['data_grid_table_virtual_key_tooltip', 'Virtueller Schlüssel'],
['data_grid_table_key_column_tooltip', 'Schlüsselspalte'],
@@ -31,6 +31,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Failed to save value'],
['data_grid_table_index_column_tooltip', 'Select whole table'],
['data_grid_table_readonly_connection_tooltip', 'Read-only connection'],
['data_grid_table_readonly_presentation_tooltip', 'Read-only: Active presentation does not support data editing'],
['data_grid_table_no_key_found_tooltip', 'No unique key was found. Data modification is not possible.'],
['data_grid_table_virtual_key_tooltip', 'Virtual key'],
['data_grid_table_key_column_tooltip', 'Key column'],
@@ -34,6 +34,7 @@ export default [
['data_grid_table_virtual_key_tooltip', 'Clé virtuelle'],
['data_grid_table_key_column_tooltip', 'Colonne clé'],
['data_grid_table_readonly_connection_tooltip', 'Connexion en lecture seule'],
['data_grid_table_readonly_presentation_tooltip', 'La présentation active ne prend pas en charge la modification des données'],
['plugin_data_spreadsheet_new_settings_disable', 'Désactiver la présentation de la table'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
@@ -30,6 +30,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Failed to save value'],
['data_grid_table_index_column_tooltip', 'Seleziona tutta la tabella'],
['data_grid_table_readonly_connection_tooltip', 'Connessione in sola lettura'],
['data_grid_table_readonly_presentation_tooltip', 'La presentazione attiva non supporta la modifica dei dati'],
['data_grid_table_no_key_found_tooltip', 'Nessuna chiave univoca è stata trovata. La modifica dei dati non è possibile.'],
['data_grid_table_virtual_key_tooltip', 'Chiave virtuale'],
['data_grid_table_key_column_tooltip', 'Colonna chiave'],
@@ -31,6 +31,7 @@ export default [
['data_grid_table_context_menu_save_value_error', 'Не удалось сохранить значение'],
['data_grid_table_index_column_tooltip', 'Выбрать всю таблицу'],
['data_grid_table_readonly_connection_tooltip', 'Подключение только для чтения'],
['data_grid_table_readonly_presentation_tooltip', 'Активное представление не поддерживает редактирование данных'],
['data_grid_table_no_key_found_tooltip', 'Не найден уникальный ключ. Изменение данных невозможно.'],
['data_grid_table_virtual_key_tooltip', 'Виртуальный ключ'],
['data_grid_table_key_column_tooltip', 'Ключевая колонка'],

Some files were not shown because too many files have changed in this diff Show More