mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7ded85ff0 | ||
|
|
6364581b5b | ||
|
|
bfcc7764d8 | ||
|
|
798adb4787 | ||
|
|
211ca65acb | ||
|
|
4c71bd6005 | ||
|
|
74a5877e6d | ||
|
|
961430f988 | ||
|
|
759b30a24b | ||
|
|
225ce91fd7 | ||
|
|
f7bdd80e9f | ||
|
|
e42d34ac58 | ||
|
|
89a1af2df9 | ||
|
|
e4260b4cd2 | ||
|
|
956ebaa515 | ||
|
|
bccf47020e | ||
|
|
dcd2b608ea | ||
|
|
84af14d2f7 | ||
|
|
f513f7dc44 | ||
|
|
ffa7f730de | ||
|
|
694d3b241f | ||
|
|
9c8f3c47e6 | ||
|
|
f918c9e25e | ||
|
|
b84031bb84 | ||
|
|
af651f9d10 | ||
|
|
3245943f3d | ||
|
|
fe01b23fce | ||
|
|
8ec052e1b1 | ||
|
|
b10b407270 | ||
|
|
5f5a6743eb | ||
|
|
4b30dc3ba5 | ||
|
|
86e114d471 | ||
|
|
d4d2e2d1fa |
@@ -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
|
||||
|
||||
@@ -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,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
+5
@@ -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);
|
||||
|
||||
+3
-1
@@ -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)) {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
+15
-9
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -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>
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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';
|
||||
|
||||
+7
-4
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+7
-4
@@ -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';
|
||||
|
||||
|
||||
+14
-7
@@ -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
-2
@@ -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:*",
|
||||
|
||||
+11
-2
@@ -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 => {
|
||||
|
||||
+2
-2
@@ -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 })} />
|
||||
|
||||
+7
-1
@@ -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>
|
||||
|
||||
+31
-7
@@ -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;
|
||||
|
||||
+5
-9
@@ -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) {
|
||||
|
||||
+11
-1
@@ -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
Reference in New Issue
Block a user