Compare commits

...
30 Commits
Author SHA1 Message Date
8f01d49f5f CB-3563 cloudbeaver preference store (#2370)
* CB-3563 cloudbeaver preference store

* CB-3563 fix cb custom drivers folder

* CB-3563 fix default property initialization

* CB-3563 :feat adds proposal insert table setting to admins settings

* CB-3563 feat: sqlEditorService has field canInsertAlias

* CB-3563 adds plain keys to the settings

* CB-3563 removes redundant alias resolver for sqlEditor

* CB-3563 fix: sql editor ctrl + z does not cause an error while SQL script typing

* CB-3563 pr fixes

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-13 20:33:52 +03:00
cb897343bd Cb 4457 subcollections firestore (#2338)
* CB-4457. Add api for read subcollections firestore

* CB-4457. Move firestore to ee module

* CB-4457. Move firestore to ee module

* CB-4457. Added annotations for firestore

* CB-4457. Added metaData

* CB-4457. Fixed build

* CB-4457 add hasChildrenCollection flag

* CB-4457. Add new resultsRow

* CB-4457. Renamed SQLResultRowMetaData parameters

* CB-4457 add rows with metadata to schema

* CB-4457. Fix gql

* CB-4457. Added logic to get Firestore subcollection

* CB-4457. Refactor result, rename gql, added new type to return gql

* CB-4457. Revert list to map

* CB-4457. Get Subcollections info

* CB-4457. Refactor code

* Added sceleton for insert subcollection

* CB-4457. Added isSupportsDataFilter flag for subcollection

* CB-4457 add locales

* CB-4457. Added isSupportsDataFilter flag for subcollection

* CB-4457. Added update/insert logic for subcollection

* CB-4457 support collection editing

* CB-4457. Refactor after review, moved hierarchical model to ee.

* CB-4457 add SerializableKey

* CB-4457 keep only new rowsWithMetaData api

* CB-4457 review fixes

* CB-4457 review fixes

* CB-4457. Refactor after review. Added logic for delete

* CB-4457 pass metadata in deleted rows

* CB-4457 check row for undefined

* CB-4457 pass metadata when editing json

* CB-4457. Refactor edit, fix icons

* CB-4457. Refactor makeDocumentInputValue, created new method for locator

* CB-4457 try to get the current metadata for row

* CB-4457 clear cache on result update

* CB-4457 remove extra check

* CB-4457. Fixed npe

---------

Co-authored-by: DenisSinelnikov <denis.sinelnikov@dbaever.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Aleksei Potsetsuev <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-02-13 18:29:22 +01:00
Alexander SkoblikovandEvgenia Bezborodova eea72bd467 CB-4673 validate default team exists (#2374)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-13 20:08:46 +03:00
b62b8d178d Cb 4375 support idle connection closing (#2368)
* CB-4365. Support idle connection closing

* CB-4375. Fix connection time

* CB-4375. Fixed checkstyle

* CB-4375. Refactor after review

* CB-4375. Refactor after review

* CB-4375. Refactor after review

* CB-4375. Refactor after review

* CB-4375. Refactor after review

* CB-4375. Refactor after review, change event type

* CB-4375. Refactor after review

---------

Co-authored-by: DenisSinelnikov <denis.sinelnikov@dbaever.com>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-13 20:08:29 +03:00
alexandDaria Marutkina 9828b1de1e CB-4567 add disableCopyData settings (#2378)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-02-13 13:45:05 +01:00
Alexey 325cbef1d8 chore: update version 23.3.5
* chore: update version 23.3.5
2024-02-13 10:47:34 +03:00
serge-rider 1e13e65205 23.3.5 version bump 2024-02-12 18:04:26 +01:00
alexandDaria Marutkina 426ab15bfc CB-4651 change wrap lines icons (#2371)
* CB-4651 change wrap lines icons

* CB-4651 remove icons from icons.svg

* CB-4651 add different size of icons

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-02-12 13:06:40 +03:00
898e7d9b9d CB-4570 add correct limits for text and blob columns in value panel (#2351)
* CB-4570 add correct limits for text and blob columns in value panel

* CB-4570 pr fixes

* CB-4570 adds shorter versions of limit messages

* CB-4570 pr fixes

* CB-4570 pr fixes

* CB-4570 code cleanup

* CB-4570 show more button moved to quota container

* CB-4570 code cleanup

* CB-4570 code cleanup

* CB-4570 quota placeholder to module css

* CB-4570 refactor: limit info in content action

* CB-4570 cleanup

* CB-4570 refactor: result set binary file implementation

* CB-4570 code clean

* CB-4570 adds QuotaPlaceholder getLimitInfo logic

* CB-4570 adds quota placeholder logic to not render when size is not truncated

* CB-4570 fix: shouldShowPasteButton reverted

* CB-4570 fix: show quota placeholder correctly

* CB-4570 code clean

* Revert "CB-4570 fix: show quota placeholder correctly"

This reverts commit d86b35e47a.

* CB-4570 fix: truncate image and text depending on current value panel presentation tab id

* Revert "Revert "CB-4570 fix: show quota placeholder correctly""

This reverts commit d5df008e89.

* CB-4570 code clean

* CB-4570 chore: dataContent has no image mentions

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-09 19:31:56 +03:00
alexandDaria Marutkina 1d497fe14f CB-4628 remove unused logic (#2367)
* CB-4628 remove unused logic

* CB-4628 remove onClean callback

* CB-4628 add applyDisabled prop

* CB-4628 check if apply is enabled on enter

* CB-4628 add disable actions prop

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-02-09 19:08:36 +03:00
Alexander Skoblikovandkseniaguzeeva 3965f16330 CB-4498 secrets cleanup (#2352)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-02-09 17:52:59 +03:00
Alexander Skoblikov 3891eed4a9 CB-4636 fix initial data configuration (#2358) 2024-02-09 14:24:18 +03:00
Serge Rider f980ee80b2 Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel 2024-02-08 13:44:24 +01:00
Serge Rider 1816016cf3 Repo dependencies 2024-02-08 13:43:07 +01:00
sergeyteleshev 9e051d5158 Merge pull request #2366 from dbeaver/CB-4633-unit-tests-licenses
CB-4633 adds licenses to unit tests
2024-02-07 17:38:27 +01:00
s.teleshev 7437b9e5eb CB-4633 adds licenses to unit tests 2024-02-07 17:37:41 +01:00
sergeyteleshevands.teleshev d96b9e94ad Cb 4633 unit tests for utils (#2357)
* CB-4633 blobToBase64 unit tests

* СB-4633 importLazyComponent tests

* CB-4633 OrderedMap unit tests

* CB-4633 removed test time limit from blobToBase64 test

* CB-4633 adds to ordered map unit tests for edge cases

* CB-4633 importLazy moved to core-blocks

* CB-4633 adds update-ts-references

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
2024-02-07 19:31:19 +03:00
7eba02ac1f CB-4535. Remove Ability to edit all users team (#2341)
* CB-4535. Remove Ability to edit all users team

* CB-4536. Fixed test

* CB-4536. Refactor after review.

* CB-4536. Refactor after review

* CB-4536. Refactor after review

* CB-4536. Refactor after review

* CB-4536. Refactor after review

* CB-4536. Refactor after review.

* CB-4536. Auto filled default group for all user when application is started

* CB-4536. Refactor after review

* CB-4536. Fixed update empty list

* CB-4536 feat: display default user team info

* CB-4536 chore: code cleanup

---------

Co-authored-by: DenisSinelnikov <denis.sinelnikov@dbaever.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
Co-authored-by: Aleksei Potsetsuev <wrouds@gmail.com>
2024-02-07 18:15:42 +03:00
Serge Rider e285ad0279 CB-4645 Null descriptions fix (#2359) 2024-02-07 17:56:49 +03:00
AnastasiyaandMashaKorax 6b2f314383 dbeaver/pro#2420 update MariaDB driver to 3.3.2 (#2362)
Co-authored-by: MashaKorax <84867187+MashaKorax@users.noreply.github.com>
2024-02-07 14:12:00 +03:00
Serge Riderandkseniaguzeeva 09e5141e37 dbeaver/pro#2403 AWS secret controller stub (#2350)
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-02-07 14:07:55 +03:00
Alexander Skoblikov 0c03d201a8 CB-4596 fix http cookies (#2363) 2024-02-06 16:50:46 +03:00
sergeyteleshevands.teleshev d96e0b812d CB-4400 removed project margin styles to prevent tree jump on DnD (#2344)
* CB-4400 removed project margin styles to prevent tree jump on DnD

* CB-4400 removed DND jump lag for connection/script/dataset trees

* CB-4400 top tree project has margin

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
2024-02-06 09:52:35 +03:00
Alexander Skoblikov 98d7d1f0d5 update sql to support oracle (#2355) 2024-02-05 15:49:01 +03:00
590830cbb5 CB-4605 fix: sql editor shows datasets panels correctly (#2347)
* CB-4605 fix: sql editor shows datasets panels correctly

* Revert "CB-4605 fix: sql editor shows datasets panels correctly"

This reverts commit 5e2afc5296.

* CB-4605 fix: TE edit dataset works fine with new dividers

* CB-4605 revert sqleditor displayed editors state

* CB-4605 removed sqlEditor hack to open full size dataset

* Revert "CB-4605 removed sqlEditor hack to open full size dataset"

This reverts commit 7ad98e42b4.

* CB-4605 sqlEditor works as it was for EE and TE

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-05 15:47:23 +03:00
Daria Marutkina 4d04f4b2ee Merge pull request #2356 from dbeaver/dariamarutkina-patch-3
CB_2334 chore: update README
2024-02-05 13:15:57 +01:00
Daria Marutkina 8cdfb12ecc CB_2334 chore: update README 2024-02-05 13:15:47 +01:00
Alexander Skoblikov 30e0bad784 fix schema version (#2354) 2024-02-05 13:41:18 +03:00
Serge RiderandEvgenia Bezborodova f8e74f926c CB-4617 NPE fix (#2349)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-02-05 12:31:30 +03:00
Alexey b7ab8b13d6 CB-4611 fix: revert font size changes (#2348) 2024-02-01 17:16:12 +03:00
175 changed files with 1710 additions and 597 deletions
+8
View File
@@ -22,6 +22,14 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 23.3.4. 2024-02-05
- Text wrap is activated by default for texts and BLOBs in the Values panel for better visibility. User can switch to the one-line mode using a button on the toolbar;
- Added the ability to edit the default preferences of the following parts: interface, tools and data viewer in the settings panel in the administrative part;
- Added ability to configure reverse proxy header name and redirect URL at logout. Admin will now be able to configure all settings;
- Update PostgreSQL driver to 42.5.2;
- Added the ability to display the OpenStreetMap layer with the default coordinate system ESPG 4326;
- ClickHouse Legacy driver has been removed to prevent potential vulnerability issues;
- Different bug fixes and enhancements have been made.
### 23.3.3. 2024-01-22
- Added password policy for the local authorization. Password parameters can be set in the configuration file;
+2 -2
View File
@@ -2,13 +2,13 @@
teams: [
{
subjectId: "admin",
name: "Admin",
teamName: "Admin",
description: "Administrative access. Has all permissions.",
permissions: [ "admin" ]
},
{
subjectId: "user",
name: "User",
teamName: "User",
description: "All users, including anonymous.",
permissions: [ ]
}
+2 -1
View File
@@ -27,5 +27,6 @@
},
plugin_data_export: {
disabled: false
}
},
sql.proposals.insert.table.alias: true
}
+2
View File
@@ -0,0 +1,2 @@
dbeaver-common
dbeaver
@@ -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.46.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.47.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.46-SNAPSHOT</version>
<version>1.0.47-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -22,19 +22,21 @@ import org.jkiss.dbeaver.model.rm.RMConstants;
/**
* General constants
*/
public class DBWConstants {
public interface DBWConstants {
public static final String PERMISSION_ADMIN = DBAPermissionRealm.PERMISSION_ADMIN;
String PERMISSION_ADMIN = DBAPermissionRealm.PERMISSION_ADMIN;
public static final String PERMISSION_CONFIGURATION_MANAGER = RMConstants.PERMISSION_CONFIGURATION_MANAGER;
public static final String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access";
String PERMISSION_CONFIGURATION_MANAGER = RMConstants.PERMISSION_CONFIGURATION_MANAGER;
String PERMISSION_PRIVATE_PROJECT_ACCESS = "private-project-access";
String PERMISSION_SECRET_MANAGER = "secret-manager";
public static final String PERMISSION_EDIT_STRUCTURE = "edit-meta";
public static final String PERMISSION_EDIT_DATA = "edit-data";
public static final String STATE_ATTR_SIGN_IN_STATE = "state.signin";
String PERMISSION_EDIT_STRUCTURE = "edit-meta";
String PERMISSION_EDIT_DATA = "edit-data";
public enum SignInState {
String STATE_ATTR_SIGN_IN_STATE = "state.signin";
enum SignInState {
GLOBAL,
EMBEDDED
}
@@ -21,8 +21,13 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.exec.DBCFeatureNotSupportedException;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.secret.DBSSecretControllerAuthorized;
import org.jkiss.dbeaver.model.secret.DBSSecretObject;
import org.jkiss.dbeaver.model.secret.DBSSecretValue;
import java.util.List;
/**
* Void secret controller.
@@ -46,6 +51,12 @@ public class VoidSecretController implements DBSSecretController, DBSSecretContr
throw new DBException("Secret controller is read-only");
}
@NotNull
@Override
public List<DBSSecretValue> discoverCurrentUserSecrets(@NotNull DBSSecretObject secretObject) throws DBException {
throw new DBCFeatureNotSupportedException("Secrets discovery not supported");
}
@Override
public void flushChanges() {
}
@@ -183,10 +183,12 @@ public class WebAppUtils {
if (sameSite != null) {
if (!request.isSecure()) {
log.debug("Attempt to set Cookie `" + cookieName + "` with `SameSite=None` failed, it require a secure context/HTTPS");
log.debug("Attempt to set Cookie `" + cookieName + "` with `SameSite=" + sameSite + "` failed, it " +
"require a secure context/HTTPS");
} else {
sessionCookie.setSecure(true);
path = path.concat("; SameSite=" + sameSite);
}
sessionCookie.setSecure(true);
path = path.concat("; SameSite=" + sameSite);
}
sessionCookie.setPath(path);
@@ -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: 23.3.4.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 23.3.5.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>23.3.4-SNAPSHOT</version>
<version>23.3.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -2,8 +2,8 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Base JDBC drivers
Bundle-SymbolicName: io.cloudbeaver.resources.drivers.base;singleton:=true
Bundle-Version: 1.0.91.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.92.qualifier
Bundle-Release-Date: 20240219
Bundle-Vendor: DBeaver Corp
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: io.cloudbeaver.resources.drivers.base
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.resources.drivers.base</artifactId>
<version>1.0.91-SNAPSHOT</version>
<version>1.0.92-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: 23.3.4.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 23.3.5.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>23.3.4-SNAPSHOT</version>
<version>23.3.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -93,6 +93,11 @@ type SQLResultColumn {
supportedOperations: [DataTypeLogicalOperation!]!
}
type SQLResultRowMetaData {
data: [Object]!
metaData: Object
}
type DatabaseDocument {
id: String
contentType: String
@@ -103,7 +108,8 @@ type DatabaseDocument {
type SQLResultSet {
id: ID!
columns: [ SQLResultColumn ]
rows: [ [ Object ] ]
rows: [ [ Object ] ] @deprecated
rowsWithMetaData: [SQLResultRowMetaData]
# True means that resultset was generated by single entity query
# New rows can be added, old rows can be deleted
@@ -112,6 +118,8 @@ type SQLResultSet {
hasMoreData: Boolean!
# can't update data or load LOB file if hasRowIdentifier = false
hasRowIdentifier: Boolean!
hasChildrenCollection: Boolean!
isSupportsDataFilter: Boolean!
}
type SQLQueryResults {
@@ -144,6 +152,12 @@ type SQLExecuteInfo {
input SQLResultRow {
data: [ Object ]!
updateValues: Object
metaData: Object
}
input SQLResultRowMetaDataInput {
data: [Object]
metaData: Object!
}
type DataTypeLogicalOperation {
@@ -386,4 +400,5 @@ extend type Mutation {
): AsyncTaskInfo!
asyncSqlRowDataCountResult(taskId: ID!): Int!
}
@@ -22,7 +22,6 @@ import com.google.gson.InstanceCreator;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.CBAuthConstants;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.service.security.PasswordPolicyConfiguration;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.model.app.WebAuthConfiguration;
@@ -31,6 +30,8 @@ import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.jetty.CBJettyServer;
import io.cloudbeaver.service.DBWServiceInitializer;
import io.cloudbeaver.service.DBWServiceServerConfigurator;
import io.cloudbeaver.service.security.CBEmbeddedSecurityController;
import io.cloudbeaver.service.security.PasswordPolicyConfiguration;
import io.cloudbeaver.service.security.SMControllerConfiguration;
import io.cloudbeaver.service.session.WebSessionManager;
import io.cloudbeaver.utils.WebAppUtils;
@@ -370,6 +371,11 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
});
System.setSecurityManager(new SecurityManager());
}
try {
addAllUsersToDefaultTeam();
} catch (DBException e) {
log.error("Failed insert default teams");
}
eventController.scheduleCheckJob();
@@ -380,6 +386,12 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
return;
}
private void addAllUsersToDefaultTeam() throws DBException {
if (securityController instanceof CBEmbeddedSecurityController<?> controller) {
controller.addAllUsersToDefaultTeam();
}
}
protected void initializeAdditionalConfiguration() {
}
@@ -543,7 +555,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
parseConfiguration(configPath.toFile());
}
// Set default preferences
PrefUtils.setDefaultPreferenceValue(ModelPreferences.getPreferences(),
PrefUtils.setDefaultPreferenceValue(DBWorkbench.getPlatform().getPreferenceStore(),
ModelPreferences.UI_DRIVERS_HOME,
getDriversLocation());
}
@@ -24,7 +24,6 @@ import io.cloudbeaver.server.jobs.WebSessionMonitorJob;
import io.cloudbeaver.service.session.WebSessionManager;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
@@ -52,9 +51,7 @@ import org.jkiss.dbeaver.runtime.SecurityProviderUtils;
import org.jkiss.dbeaver.runtime.qm.QMLogFileWriter;
import org.jkiss.dbeaver.runtime.qm.QMRegistryImpl;
import org.jkiss.dbeaver.utils.ContentUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.IOUtils;
import org.osgi.framework.Bundle;
import java.io.IOException;
import java.nio.file.Files;
@@ -75,8 +72,6 @@ public class CBPlatform extends BasePlatformImpl {
public static final String WORK_DATA_FOLDER_NAME = ".work-data";
static CBPlatform instance;
@Nullable
private static CBApplication application = null;
@@ -86,44 +81,11 @@ public class CBPlatform extends BasePlatformImpl {
private QMLogFileWriter qmLogWriter;
private DBACertificateStorage certificateStorage;
private WebGlobalWorkspace workspace;
private CBPreferenceStore preferenceStore;
private final List<DBPDriver> applicableDrivers = new ArrayList<>();
public static CBPlatform getInstance() {
if (instance == null) {
synchronized (CBPlatform.class) {
if (instance == null) {
// Initialize CB platform
CBPlatform.createInstance();
}
}
}
return instance;
}
private static CBPlatform createInstance() {
log.debug("Initializing product: " + GeneralUtils.getProductTitle());
if (Platform.getProduct() != null) {
Bundle definingBundle = Platform.getProduct().getDefiningBundle();
if (definingBundle != null) {
log.debug("Host plugin: " + definingBundle.getSymbolicName() + " " + definingBundle.getVersion());
} else {
log.debug("!!! No product bundle found");
}
}
try {
instance = new CBPlatform();
instance.initialize();
return instance;
} catch (Throwable e) {
log.error("Error initializing CBPlatform", e);
throw new IllegalStateException("Error initializing CBPlatform", e);
}
}
public static DBPPreferenceStore getGlobalPreferenceStore() {
return WebPlatformActivator.getInstance().getPreferences();
return (CBPlatform) DBWorkbench.getPlatform();
}
CBPlatform() {
@@ -134,10 +96,10 @@ public class CBPlatform extends BasePlatformImpl {
}
@Override
protected void initialize() {
protected synchronized void initialize() {
long startTime = System.currentTimeMillis();
log.info("Initialize web platform...");
log.info("Initialize web platform...: ");
this.preferenceStore = new CBPreferenceStore(this, WebPlatformActivator.getInstance().getPreferences());
// Register BC security provider
SecurityProviderUtils.registerSecurityProvider();
@@ -153,7 +115,6 @@ public class CBPlatform extends BasePlatformImpl {
this.certificateStorage = new DefaultCertificateStorage(
WebPlatformActivator.getInstance().getStateLocation().toFile().toPath().resolve("security"));
super.initialize();
refreshApplicableDrivers();
@@ -218,7 +179,6 @@ public class CBPlatform extends BasePlatformImpl {
}
CBPlatform.application = null;
CBPlatform.instance = null;
System.gc();
log.debug("Shutdown completed in " + (System.currentTimeMillis() - startTime) + "ms");
}
@@ -253,7 +213,7 @@ public class CBPlatform extends BasePlatformImpl {
@NotNull
@Override
public DBPPreferenceStore getPreferenceStore() {
return WebPlatformActivator.getInstance().getPreferences();
return preferenceStore;
}
@NotNull
@@ -1,5 +1,3 @@
package io.cloudbeaver.server;
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
@@ -17,6 +15,10 @@ package io.cloudbeaver.server;
* limitations under the License.
*/
package io.cloudbeaver.server;
import org.jkiss.dbeaver.runtime.DBWorkbench;
/**
* The activator class controls the plug-in life cycle
*/
@@ -26,8 +28,8 @@ public class CBPlatformActivator extends WebPlatformActivator {
protected void shutdownPlatform() {
try {
// Dispose core
if (CBPlatform.instance != null) {
CBPlatform.instance.dispose();
if (DBWorkbench.getPlatform() instanceof CBPlatform cbPlatform) {
cbPlatform.dispose();
}
} catch (Throwable e) {
e.printStackTrace();
@@ -0,0 +1,197 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.impl.preferences.AbstractPreferenceStore;
import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore;
import java.io.IOException;
import java.util.Map;
public class CBPreferenceStore extends AbstractPreferenceStore {
@NotNull
private final CBPlatform cbPlatform;
private final DBPPreferenceStore parentStore;
public CBPreferenceStore(
@NotNull CBPlatform cbPlatform,
@NotNull DBPPreferenceStore parentStore
) {
this.cbPlatform = cbPlatform;
this.parentStore = parentStore;
}
@Override
public boolean contains(String name) {
return productConf().containsKey(name);
}
@Override
public boolean getBoolean(String name) {
return toBoolean(getString(name));
}
@Override
public double getDouble(String name) {
return toDouble(getString(name));
}
@Override
public float getFloat(String name) {
return toFloat(getString(name));
}
@Override
public int getInt(String name) {
return toInt(getString(name));
}
@Override
public long getLong(String name) {
return toLong(getString(name));
}
@Override
public String getString(String name) {
Object value = productConf().getOrDefault(name, parentStore.getString(name));
if (value == null) {
return null;
}
return value.toString();
}
@Override
public boolean getDefaultBoolean(String name) {
return getBoolean(name);
}
@Override
public double getDefaultDouble(String name) {
return getDouble(name);
}
@Override
public float getDefaultFloat(String name) {
return getFloat(name);
}
@Override
public int getDefaultInt(String name) {
return getInt(name);
}
@Override
public long getDefaultLong(String name) {
return getLong(name);
}
@Override
public String getDefaultString(String name) {
// TODO: split product.conf and runtime.product.conf
return getString(name);
}
@Override
public boolean isDefault(String name) {
return true;
}
@Override
public boolean needsSaving() {
return false;
}
@Override
public void setDefault(String name, double value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, float value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, int value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, long value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setDefault(String name, String defaultObject) {
this.parentStore.setDefault(name, defaultObject);
}
@Override
public void setDefault(String name, boolean value) {
setDefault(name, String.valueOf(value));
}
@Override
public void setToDefault(String name) {
parentStore.setToDefault(name);
}
@Override
public void setValue(String name, double value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, float value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, int value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, long value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, String value) {
parentStore.setValue(name, value);
}
@Override
public void setValue(String name, boolean value) {
parentStore.setValue(name, value);
}
@Override
public void save() throws IOException {
throw new RuntimeException("Not Implemented");
}
public CBApplication getApp() {
return cbPlatform.getApplication();
}
private Map<String, Object> productConf() {
var app = cbPlatform.getApplication();
return app.getProductConfiguration();
}
}
@@ -17,11 +17,21 @@
package io.cloudbeaver.server.jobs;
import io.cloudbeaver.model.session.BaseWebSession;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.app.DBPPlatform;
import org.jkiss.dbeaver.model.auth.SMSession;
import org.jkiss.dbeaver.model.websocket.event.WSEventType;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceEvent;
import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceProperty;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.jobs.DataSourceMonitorJob;
import java.util.Collection;
import java.util.List;
/**
* Web data source monitor job.
@@ -35,8 +45,23 @@ public class WebDataSourceMonitorJob extends DataSourceMonitorJob {
@Override
protected void doJob() {
Collection<BaseWebSession> allSessions = CBPlatform.getInstance().getSessionManager().getAllActiveSessions();
allSessions.parallelStream().forEach(
s -> checkDataSourceAliveInWorkspace(s.getWorkspace())
);
allSessions.parallelStream().forEach(s -> {
checkDataSourceAliveInWorkspace(s.getWorkspace(), s::getLastAccessTimeMillis);
});
}
@Override
public void showNotification(DBPDataSource dataSource, DBPDataSourceContainer dsDescriptor, SMSession smSession) {
if (smSession instanceof WebSession webSession) {
webSession.addSessionEvent( //TODO: Add new event for disconnect datasource
WSDataSourceEvent.update(
webSession.getSessionId(),
webSession.getUserId(),
dsDescriptor.getProject().getId(),
List.of(dsDescriptor.getId()),
WSDataSourceProperty.CONFIGURATION)
);
}
}
}
@@ -331,7 +331,7 @@ public class WebSQLProcessor implements WebSessionProvider {
{
// we don't need to add same row several times
// (it can be when we update the row from RS with several tables)
Set<Object[]> newResultSetRows = new LinkedHashSet<>();
Set<WebSQLQueryResultSetRow> newResultSetRows = new LinkedHashSet<>();
KeyDataReceiver keyReceiver = new KeyDataReceiver(contextInfo.getResults(resultsId));
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
@@ -372,7 +372,7 @@ public class WebSQLProcessor implements WebSessionProvider {
totalUpdateCount += statistics.getRowsUpdated();
result.setDuration(result.getDuration() + statistics.getExecuteTime());
newResultSetRows.add(rowValues);
newResultSetRows.add(new WebSQLQueryResultSetRow(rowValues, null));
}
if (txnManager != null && txnManager.isSupportsTransactions()) {
@@ -399,7 +399,7 @@ public class WebSQLProcessor implements WebSessionProvider {
WebSQLQueryResults updateResults = new WebSQLQueryResults(webSession, dataFormat);
updateResults.setUpdateRowCount(totalUpdateCount);
updateResults.setResultSet(updatedResultSet);
updatedResultSet.setRows(newResultSetRows.toArray(new Object[0][]));
updatedResultSet.setRows(List.of(newResultSetRows.toArray(new WebSQLQueryResultSetRow[0])));
queryResults.add(updateResults);
@@ -410,7 +410,7 @@ public class WebSQLProcessor implements WebSessionProvider {
private void getUpdatedRowsInfo(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull Set<Object[]> newResultSetRows,
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
@Nullable WebDataFormat dataFormat,
@NotNull DBRProgressMonitor monitor)
throws DBCException {
@@ -420,8 +420,8 @@ public class WebSQLProcessor implements WebSessionProvider {
"Refresh row(s) after insert/update")
) {
boolean canRefreshResults = resultsInfo.canRefreshResults();
for (Object[] row : newResultSetRows) {
if (row.length == 0) {
for (WebSQLQueryResultSetRow row : newResultSetRows) {
if (row.getData().length == 0) {
continue;
}
if (!canRefreshResults) {
@@ -438,7 +438,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (attr.getRowIdentifier() == null) {
continue;
}
final Object keyValue = row[attr.getOrdinalPosition()];
final Object keyValue = row.getData()[attr.getOrdinalPosition()];
if (DBUtils.isNullValue(keyValue)) {
hasKey = false;
break;
@@ -455,7 +455,7 @@ public class WebSQLProcessor implements WebSessionProvider {
}
DBDDataFilter filter = new DBDDataFilter(constraints);
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
WebRowDataReceiver dataReceiver = new WebRowDataReceiver(resultsInfo.getAttributes(), row, dataFormat);
WebRowDataReceiver dataReceiver = new WebRowDataReceiver(resultsInfo.getAttributes(), row.getData(), dataFormat);
dataContainer.readData(
new AbstractExecutionSource(dataContainer, getExecutionContext(dataContainer), this),
session,
@@ -471,14 +471,14 @@ public class WebSQLProcessor implements WebSessionProvider {
private void makeWebCellRow(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull Object[] row,
@NotNull WebSQLQueryResultSetRow row,
@Nullable WebDataFormat dataFormat
) throws DBCException {
for (int i = 0; i < row.length; i++) {
row[i] = WebSQLUtils.makeWebCellValue(
for (int i = 0; i < row.getData().length; i++) {
row.getData()[i] = WebSQLUtils.makeWebCellValue(
webSession,
resultsInfo.getAttributeByPosition(i),
row[i],
row.getData()[i],
dataFormat);
}
}
@@ -563,6 +563,14 @@ public class WebSQLProcessor implements WebSessionProvider {
Map<String, Object> updateValues = row.getUpdateValues().entrySet().stream()
.filter(x -> CommonUtils.equalObjects(allAttributes[CommonUtils.toInt(x.getKey())].getRowIdentifier(), rowIdentifier))
.collect(HashMap::new, (m,v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
Map<String, Object> metaData;
if (row.getMetaData() != null) {
metaData = new HashMap<>(row.getMetaData());
} else {
metaData = new HashMap<>();
}
if (finalRow.length == 0 || CommonUtils.isEmpty(updateValues)) {
continue;
}
@@ -582,7 +590,7 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentValue = keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator;
if (isDocumentValue) {
rowValues[updateAttributes.length + i] =
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row);
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row, metaData);
} else {
rowValues[updateAttributes.length + i] = keyAttribute.getValueHandler().getValueFromObject(
session,
@@ -651,6 +659,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (keyAttributes.length > 0 && !CommonUtils.isEmpty(deletedRows)) {
for (WebSQLResultsRow row : deletedRows) {
Object[] keyData = row.getData();
Map<String, Object> keyMetaData = row.getMetaData();
if (keyData.length == 0) {
continue;
}
@@ -658,20 +667,37 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentKey = keyAttributes.length == 1 && keyAttributes[0].getDataKind() == DBPDataKind.DOCUMENT;
for (int i = 0; i < allAttributes.length; i++) {
if (isDocumentKey || ArrayUtils.contains(keyAttributes, allAttributes[i])) {
Object realCellValue = convertInputCellValue(session, allAttributes[i],
keyData[i], withoutExecution);
delKeyAttributes.put(allAttributes[i], realCellValue);
if (dataContainer instanceof DBSDocumentLocator dataLocator) {
Map<String, Object> keyMap = new LinkedHashMap<>();
DBDAttributeBinding[] attributes = resultsInfo.getAttributes();
for (int j = 0; j < attributes.length; j++) {
DBDAttributeBinding attr = attributes[j];
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData()[j]);
keyMap.put(attr.getName(), plainValue);
}
}
DBDDocument document = dataLocator.findDocument(session, keyMap, keyMetaData);
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
delKeyAttributes.keySet().toArray(new DBSAttributeBase[0]),
executionSource);
deleteBatch.add(delKeyAttributes.values().toArray());
resultBatches.put(deleteBatch, new Object[0]);
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
keyAttributes,
executionSource);
deleteBatch.add(new Object[] {document});
resultBatches.put(deleteBatch, new Object[0]);
} else {
for (int i = 0; i < allAttributes.length; i++) {
if (isDocumentKey || ArrayUtils.contains(keyAttributes, allAttributes[i])) {
Object realCellValue = convertInputCellValue(session, allAttributes[i],
keyData[i], withoutExecution);
delKeyAttributes.put(allAttributes[i], realCellValue);
}
}
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
delKeyAttributes.keySet().toArray(new DBSAttributeBase[0]),
executionSource);
deleteBatch.add(delKeyAttributes.values().toArray());
resultBatches.put(deleteBatch, new Object[0]);
}
}
}
}
@@ -693,7 +719,8 @@ public class WebSQLProcessor implements WebSessionProvider {
DBCSession session,
DBSDocumentLocator dataContainer,
WebSQLResultsInfo resultsInfo,
WebSQLResultsRow row) throws DBException
WebSQLResultsRow row,
Map<String, Object> metaData) throws DBException
{
// Document reference
DBDDocument document = null;
@@ -702,16 +729,18 @@ public class WebSQLProcessor implements WebSessionProvider {
for (int j = 0; j < attributes.length; j++) {
DBDAttributeBinding attr = attributes[j];
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData()[j]);
if (plainValue instanceof DBDDocument) {
if (plainValue instanceof DBDDocument dbdDocument) {
// FIXME: Hack for DynamoDB. We pass entire document as a key
// FIXME: Let's just return it back for now
document = (DBDDocument) plainValue;
break;
if (dataContainer.isDocumentValid(dbdDocument)) {
document = (DBDDocument) plainValue;
break;
}
}
keyMap.put(attr.getName(), plainValue);
}
if (document == null) {
document = dataContainer.findDocument(session.getProgressMonitor(), keyMap);
document = dataContainer.findDocument(session, keyMap, metaData);
if (document == null) {
throw new DBCException("Error finding document by key " + keyMap);
}
@@ -826,7 +855,7 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentValue = keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator;
if (isDocumentValue) {
rowValues[i] =
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row);
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row, null);
} else {
Object inputCellValue = row.getData()[keyAttribute.getOrdinalPosition()];
@@ -25,13 +25,15 @@ import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.data.DBDValueError;
import org.jkiss.dbeaver.model.meta.MetaData;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
class WebSQLQueryDataReceiver implements DBDDataReceiver {
private static final Log log = Log.getLog(WebSQLQueryDataReceiver.class);
@@ -42,7 +44,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
private DBDAttributeBinding[] bindings;
private List<Object[]> rows = new ArrayList<>();
private List<WebSQLQueryResultSetRow> rows = new ArrayList<>();
private final Number rowLimit;
WebSQLQueryDataReceiver(WebSQLContextInfo contextInfo, DBSDataContainer dataContainer, WebDataFormat dataFormat) {
@@ -70,6 +72,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
@Override
public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException {
Map<String, Object> metaDataMap = null;
Object[] row = new Object[bindings.length];
for (int i = 0; i < bindings.length; i++) {
@@ -81,12 +84,25 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
binding.getMetaAttribute(),
i);
row[i] = cellValue;
if (cellValue != null) {
Method[] methods = cellValue.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MetaData.class)) {
if (metaDataMap == null) {
metaDataMap = new HashMap<>();
}
Object value = method.invoke(cellValue);
metaDataMap.put(method.getAnnotation(MetaData.class).name(), value);
}
}
}
} catch (Throwable e) {
row[i] = new DBDValueError(e);
}
}
rows.add(row);
rows.add(new WebSQLQueryResultSetRow(row, metaDataMap));
if (rowLimit != null && rows.size() > rowLimit.longValue()) {
throw new DBQuotaException(
@@ -101,7 +117,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
DBSEntity entity = dataContainer instanceof DBSEntity ? (DBSEntity) dataContainer : null;
try {
DBExecUtils.bindAttributes(session, entity, resultSet, bindings, rows);
DBExecUtils.bindAttributes(session, entity, resultSet, bindings, rows.stream().map(WebSQLQueryResultSetRow::getData).collect(Collectors.toList()));
} catch (DBException e) {
log.error("Error binding attributes", e);
}
@@ -122,15 +138,17 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
}
// Convert row values
for (Object[] row : rows) {
for (WebSQLQueryResultSetRow row : rows) {
for (int i = 0; i < bindings.length; i++) {
DBDAttributeBinding binding = bindings[i];
row[i] = WebSQLUtils.makeWebCellValue(webSession, binding, row[i], dataFormat);
row.getData()[i] = WebSQLUtils.makeWebCellValue(webSession, binding, row.getData()[i], dataFormat);
}
}
webResultSet.setColumns(bindings);
webResultSet.setRows(rows.toArray(new Object[0][]));
webResultSet.setRows(List.of(rows.toArray(new WebSQLQueryResultSetRow[0])));
webResultSet.setHasChildrenCollection(resultSet instanceof DBDSubCollectionResultSet);
webResultSet.setSupportsDataFilter(dataContainer.isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER));
WebSQLResultsInfo resultsInfo = contextInfo.saveResult(dataContainer, bindings);
webResultSet.setResultsInfo(resultsInfo);
@@ -157,14 +175,14 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
// Convert original rows into new rows with leaf attributes
// Extract values for leaf attributes from original row
DBDAttributeBinding[] leafAttributes = leafBindings.toArray(new DBDAttributeBinding[0]);
List<Object[]> newRows = new ArrayList<>();
for (Object[] row : rows) {
List<WebSQLQueryResultSetRow> newRows = new ArrayList<>();
for (WebSQLQueryResultSetRow row : rows) {
Object[] newRow = new Object[leafBindings.size()];
for (int i = 0; i < leafBindings.size(); i++) {
DBDAttributeBinding leafAttr = leafBindings.get(i);
try {
//Object topValue = row[leafAttr.getTopParent().getOrdinalPosition()];
Object cellValue = DBUtils.getAttributeValue(leafAttr, leafAttributes, row);
Object cellValue = DBUtils.getAttributeValue(leafAttr, leafAttributes, row.getData());
/*
Object cellValue = leafAttr.getValueHandler().getValueFromObject(
session,
@@ -178,7 +196,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
newRow[i] = new DBDValueError(e);
}
}
newRows.add(newRow);
newRows.add(new WebSQLQueryResultSetRow(newRow, row.getMetaData()));
}
this.bindings = leafAttributes;
this.rows = newRows;
@@ -19,6 +19,11 @@ package io.cloudbeaver.service.sql;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.Pair;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Web SQL query resultset.
@@ -28,12 +33,15 @@ public class WebSQLQueryResultSet {
private static final Log log = Log.getLog(WebSQLQueryResultSet.class);
private WebSQLQueryResultColumn[] columns;
private Object[][] rows;
private List<WebSQLQueryResultSetRow> rows;
private boolean hasMoreData;
private WebSQLResultsInfo resultsInfo;
private boolean singleEntity = true;
private boolean hasRowIdentifier;
private boolean hasChildrenCollection;
private boolean isSupportsDataFilter;
public WebSQLQueryResultSet() {
}
@@ -60,11 +68,17 @@ public class WebSQLQueryResultSet {
}
@Property
@Deprecated
public Object[][] getRows() {
return rows.stream().map(WebSQLQueryResultSetRow::getData).toArray(x -> new Object[x][1]);
}
@Property
public List<WebSQLQueryResultSetRow> getRowsWithMetaData() {
return rows;
}
public void setRows(Object[][] rows) {
public void setRows(List<WebSQLQueryResultSetRow> rows) {
this.rows = rows;
}
@@ -102,4 +116,22 @@ public class WebSQLQueryResultSet {
public void setHasRowIdentifier(boolean hasRowIdentifier) {
this.hasRowIdentifier = hasRowIdentifier;
}
@Property
public boolean isHasChildrenCollection() {
return hasChildrenCollection;
}
public void setHasChildrenCollection(boolean hasSuCollection) {
this.hasChildrenCollection = hasSuCollection;
}
@Property
public boolean isSupportsDataFilter() {
return isSupportsDataFilter;
}
public void setSupportsDataFilter(boolean supportsDataFilter) {
isSupportsDataFilter = supportsDataFilter;
}
}
@@ -0,0 +1,31 @@
package io.cloudbeaver.service.sql;
import java.util.Map;
public class WebSQLQueryResultSetRow {
private Object[] data;
private Map<String, Object> metaData;
public WebSQLQueryResultSetRow(Object[] data, Map<String, Object> metaData) {
this.data = data;
this.metaData = metaData;
}
public Object[] getData() {
return data;
}
public Map<String, Object> getMetaData() {
return metaData;
}
public void setData(Object[] data) {
this.data = data;
}
public void setMetaData(Map<String, Object> metaData) {
this.metaData = metaData;
}
}
@@ -25,9 +25,11 @@ import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.data.DBDDocument;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Web SQL query results.
@@ -86,16 +88,16 @@ public class WebSQLQueryResults {
}
List<WebSQLDatabaseDocument> documents = new ArrayList<>();
for (Object[] row : resultSet.getRows()) {
if (row.length != 1) {
for (WebSQLQueryResultSetRow row : resultSet.getRowsWithMetaData()) {
if (row.getData().length != 1) {
log.debug("Non-document row content");
}
if (row[0] == null) {
if (row.getData()[0] == null) {
documents.add(null);
} else if (row[0] instanceof DBDDocument) {
documents.add(new WebSQLDatabaseDocument(webSession, (DBDDocument) row[0]));
} else if (row.getData()[0] instanceof DBDDocument) {
documents.add(new WebSQLDatabaseDocument(webSession, (DBDDocument) row.getData()[0]));
} else {
log.debug("Non-document row value: " + row[0].getClass().getName());
log.debug("Non-document row value: " + row.getData()[0].getClass().getName());
}
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.util.Map;
@@ -28,12 +29,16 @@ public class WebSQLResultsRow {
private Object[] data;
private Map<String, Object> updateValues;
@Nullable
private Map<String, Object> metaData;
public WebSQLResultsRow() {
}
public WebSQLResultsRow(Map<String, Object> map) {
data = JSONUtils.getObjectList(map, "data").toArray();
updateValues = JSONUtils.getObject(map, "updateValues");
metaData = JSONUtils.getObject(map, "metaData");
}
public Object[] getData() {
@@ -43,4 +48,9 @@ public class WebSQLResultsRow {
public Map<String, Object> getUpdateValues() {
return updateValues;
}
@Nullable
public Map<String, Object> getMetaData() {
return metaData;
}
}
@@ -33,9 +33,7 @@ import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -50,6 +48,7 @@ import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorConfigurationRegistry;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorDescriptor;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSWrapper;
import org.jkiss.dbeaver.utils.RuntimeUtils;
@@ -60,9 +59,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -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.90.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.91.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.admin</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.91-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -44,6 +44,7 @@ import org.jkiss.dbeaver.model.auth.AuthInfo;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.security.*;
import org.jkiss.dbeaver.model.security.user.SMTeam;
import org.jkiss.dbeaver.model.security.user.SMUser;
@@ -178,6 +179,10 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
webSession.addInfoMessage("Delete user - " + userName);
try {
var secretController = DBSSecretController.getSessionSecretControllerOrNull(webSession);
if (secretController != null) {
secretController.deleteSubjectSecrets(userName);
}
webSession.getAdminSecurityController().deleteUser(userName);
} catch (Exception e) {
throw new DBWebException("Error deleting user", e);
@@ -235,6 +240,10 @@ public class WebServiceAdmin implements DBWServiceAdmin {
if (Arrays.stream(userTeams).anyMatch(team -> team.getTeamId().equals(teamId))) {
throw new DBWebException("You can not delete your own team");
}
var secretController = DBSSecretController.getSessionSecretControllerOrNull(webSession);
if (secretController != null) {
secretController.deleteSubjectSecrets(teamId);
}
adminSecurityController.deleteTeam(teamId, force);
return true;
} catch (Exception 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.90.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.91.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.auth</artifactId>
<version>1.0.90-SNAPSHOT</version>
<version>1.0.91-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.91.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.92.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
<version>1.0.91-SNAPSHOT</version>
<version>1.0.92-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.8.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.9.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.fs</artifactId>
<version>1.0.8-SNAPSHOT</version>
<version>1.0.9-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: DBeaver Corp
Bundle-Name: Cloudbeaver Web Service - Metadata
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
Bundle-Version: 1.0.94.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.95.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.metadata</artifactId>
<version>1.0.94-SNAPSHOT</version>
<version>1.0.95-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.8.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.9.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm.nio</artifactId>
<version>1.0.8-SNAPSHOT</version>
<version>1.0.9-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.43.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.44.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.rm</artifactId>
<version>1.0.43-SNAPSHOT</version>
<version>1.0.44-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -31,6 +31,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMResource;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.security.*;
import org.jkiss.dbeaver.model.websocket.WSConstants;
import org.jkiss.dbeaver.model.websocket.event.WSProjectUpdateEvent;
@@ -265,6 +266,14 @@ public class WebServiceRM implements DBWServiceRM {
@Override
public boolean deleteProject(@NotNull WebSession session, @NotNull String projectId) throws DBWebException {
try {
var project = session.getProjectById(projectId);
if (project == null) {
throw new DBException("Project not found: " + projectId);
}
if (project.isUseSecretStorage()) {
var secretController = DBSSecretController.getProjectSecretController(project);
secretController.deleteProjectSecrets(project.getId());
}
getResourceController(session).deleteProject(projectId);
session.removeSessionProject(projectId);
WebAppUtils.getWebApplication().getEventController().addEvent(
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
Bundle-Vendor: Cloudbeaver Web Service - Security
Bundle-Vendor: DBeaver Corp
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
Bundle-Version: 1.0.46.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.47.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -110,8 +110,8 @@ CREATE TABLE {table_prefix}CB_CREDENTIALS_PROFILE
(
PROFILE_ID VARCHAR(128) NOT NULL,
PROFILE_NAME VARCHAR(100) NOT NULL,
PROFILE_DESCRIPTION VARCHAR(255) NOT NULL,
PARENT_PROFILE_ID VARCHAR(128) NULL,
PROFILE_DESCRIPTION VARCHAR(255),
PARENT_PROFILE_ID VARCHAR(128),
CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (PROFILE_ID),
@@ -148,7 +148,7 @@ CREATE TABLE {table_prefix}CB_TEAM
(
TEAM_ID VARCHAR(128) NOT NULL,
TEAM_NAME VARCHAR(100) NOT NULL,
TEAM_DESCRIPTION VARCHAR(255) NOT NULL,
TEAM_DESCRIPTION VARCHAR(255),
CREATE_TIME TIMESTAMP NOT NULL,
PRIMARY KEY (TEAM_ID),
@@ -1,12 +1,12 @@
ALTER TABLE {table_prefix}CB_AUTH_TOKEN ADD COLUMN AUTH_ROLE VARCHAR(32);
ALTER TABLE {table_prefix}CB_AUTH_TOKEN ADD AUTH_ROLE VARCHAR(32);
ALTER TABLE {table_prefix}CB_USER ADD COLUMN DEFAULT_AUTH_ROLE VARCHAR(32) NULL;
ALTER TABLE {table_prefix}CB_USER ADD DEFAULT_AUTH_ROLE VARCHAR(32) NULL;
CREATE TABLE {table_prefix}CB_TEAM
(
TEAM_ID VARCHAR(128) NOT NULL,
TEAM_NAME VARCHAR(100) NOT NULL,
TEAM_DESCRIPTION VARCHAR(255) NOT NULL,
TEAM_DESCRIPTION VARCHAR(255),
CREATE_TIME TIMESTAMP NOT NULL,
@@ -1,2 +1,2 @@
ALTER TABLE {table_prefix}CB_USER_SECRETS
ADD COLUMN ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL;
ADD ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL;
@@ -1,2 +1,2 @@
ALTER TABLE {table_prefix}CB_AUTH_ATTEMPT
ADD COLUMN IS_MAIN_AUTH CHAR(1) DEFAULT 'Y' NOT NULL;
ADD IS_MAIN_AUTH CHAR(1) DEFAULT 'Y' NOT NULL;
@@ -1,2 +1,2 @@
ALTER TABLE {table_prefix}CB_AUTH_ATTEMPT
ADD COLUMN AUTH_USERNAME VARCHAR(128) NULL;
ADD AUTH_USERNAME VARCHAR(128) NULL;
@@ -26,4 +26,4 @@ SELECT USER_ID, SECRET_ID, SECRET_VALUE, ENCODING_TYPE, UPDATE_TIME, UPDATE_TIME
DROP TABLE {table_prefix}CB_USER_SECRETS;
ALTER TABLE {table_prefix}CB_AUTH_SUBJECT
ADD COLUMN IS_SECRET_STORAGE CHAR(1) DEFAULT 'Y' NOT NULL;
ADD IS_SECRET_STORAGE CHAR(1) DEFAULT 'Y' NOT NULL;
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.46-SNAPSHOT</version>
<version>1.0.47-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -210,7 +210,6 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
application.getEventController().addEvent(event);
}
@Override
public void setUserTeams(String userId, String[] teamIds, String grantorId) throws DBCException {
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
@@ -230,6 +229,10 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
database.normalizeTableNames("DELETE FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"),
userId
);
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
if (CommonUtils.isNotEmpty(defaultUserTeam) && !ArrayUtils.contains(teamIds, defaultUserTeam)) {
teamIds = ArrayUtils.add(String.class, teamIds, defaultUserTeam);
}
if (!ArrayUtils.isEmpty(teamIds)) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("INSERT INTO {table_prefix}CB_USER_TEAM" +
@@ -246,6 +249,51 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
}
}
public void addAllUsersToDefaultTeam() throws DBCException {
if (application.isConfigurationMode()) {
return;
}
if (CommonUtils.isEmpty(application.getAppConfiguration().getDefaultUserTeam())) {
return;
}
try (Connection dbCon = database.openConnection()) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID \n" +
"FROM {table_prefix}CB_USER\n" +
"WHERE USER_ID NOT IN (\n" +
" SELECT USER_ID FROM {table_prefix}CB_USER_TEAM CUT WHERE CUT.TEAM_ID = ? \n" +
")")
)) {
dbStat.setString(1, application.getAppConfiguration().getDefaultUserTeam());
ResultSet dbResult = dbStat.executeQuery();
List<String> usersIds = new ArrayList<>();
while (dbResult.next()) {
String userId = dbResult.getString(1);
usersIds.add(userId);
}
if (usersIds.isEmpty()) {
return;
}
for (String usersId : usersIds) {
try (PreparedStatement insertStat = dbCon.prepareStatement(
database.normalizeTableNames("INSERT INTO {table_prefix}CB_USER_TEAM(USER_ID, TEAM_ID, GRANT_TIME, GRANTED_BY)" +
" VALUES(?,?,?,?)"))) {
insertStat.setString(1, usersId);
insertStat.setString(2, application.getAppConfiguration().getDefaultUserTeam());
insertStat.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
insertStat.setString(4, "CloudBeaver Application");
insertStat.executeUpdate();
}
}
}
} catch (SQLException e) {
throw new DBCException("Error while setting default user teams", e);
}
}
@NotNull
@Override
@@ -1012,7 +1060,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
txn.commit();
}
} catch (SQLException e) {
throw new DBCException("Error saving tem in database", e);
throw new DBCException("Error saving team in database", e);
}
}
@@ -1410,10 +1458,12 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
String authAttemptId = UUID.randomUUID().toString();
try (Connection dbCon = database.openConnection()) {
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
if (smConfig.isCheckBruteforce()
&& this.getAuthProvider(authProviderId).getInstance() instanceof SMBruteForceProtected bruteforceProtected) {
BruteForceUtils.checkBruteforce(smConfig,
getLatestUserLogins(dbCon, authProviderId, bruteforceProtected.getInputUsername(authData).toString()));
if (smConfig.isCheckBruteforce() && this.getAuthProvider(authProviderId).getInstance() instanceof SMBruteForceProtected bruteforceProtected) {
Object inputUsername = bruteforceProtected.getInputUsername(authData);
if (inputUsername != null) {
BruteForceUtils.checkBruteforce(smConfig,
getLatestUserLogins(dbCon, authProviderId, inputUsername.toString()));
}
}
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames(
@@ -1435,7 +1485,12 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
}
dbStat.setString(7, isMainSession ? CHAR_BOOL_TRUE : CHAR_BOOL_FALSE);
if (this.getAuthProvider(authProviderId).getInstance() instanceof SMBruteForceProtected bruteforceProtected) {
dbStat.setString(8, bruteforceProtected.getInputUsername(authData).toString());
Object inputUsername = bruteforceProtected.getInputUsername(authData);
if (inputUsername != null) {
dbStat.setString(8, inputUsername.toString());
} else {
dbStat.setString(8, null);
}
} else {
dbStat.setString(8, null);
}
@@ -33,6 +33,7 @@ import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBConstants;
import org.jkiss.dbeaver.model.auth.AuthInfo;
import org.jkiss.dbeaver.model.connection.DBPDriver;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -73,7 +74,7 @@ public class CBDatabase {
public static final String SCHEMA_UPDATE_SQL_PATH = "db/cb_schema_update_";
private static final int LEGACY_SCHEMA_VERSION = 1;
private static final int CURRENT_SCHEMA_VERSION = 17;
private static final int CURRENT_SCHEMA_VERSION = 16;
private static final String DEFAULT_DB_USER_NAME = "cb-data";
private static final String DEFAULT_DB_PWD_FILE = ".database-credentials.dat";
@@ -163,7 +164,11 @@ public class CBDatabase {
}
if (H2Migrator.isH2Database(databaseConfiguration)) {
var migrator = new H2Migrator(monitor, dataSourceProviderRegistry, databaseConfiguration, dbURL, dbProperties);
var migrator = new H2Migrator(monitor,
dataSourceProviderRegistry,
databaseConfiguration,
dbURL,
dbProperties);
migrator.migrateDatabaseIfNeeded(V1_DB_NAME, V2_DB_NAME);
}
@@ -302,7 +307,10 @@ public class CBDatabase {
if (adminUser == null) {
adminUser = new SMUser(adminName, true, "ADMINISTRATOR");
adminSecurityController.createUser(adminUser.getUserId(), adminUser.getMetaParameters(), true, adminUser.getAuthRole());
adminSecurityController.createUser(adminUser.getUserId(),
adminUser.getMetaParameters(),
true,
adminUser.getAuthRole());
}
if (!CommonUtils.isEmpty(adminPassword)) {
@@ -313,7 +321,8 @@ public class CBDatabase {
credentials.put(LocalAuthProviderConstants.CRED_USER, adminUser.getUserId());
credentials.put(LocalAuthProviderConstants.CRED_PASSWORD, clientPassword);
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance().getAuthProvider(LocalAuthProviderConstants.PROVIDER_ID);
WebAuthProviderDescriptor authProvider = WebAuthProviderRegistry.getInstance()
.getAuthProvider(LocalAuthProviderConstants.PROVIDER_ID);
if (authProvider != null) {
adminSecurityController.setUserCredentials(adminUser.getUserId(), authProvider.getId(), credentials);
}
@@ -347,7 +356,8 @@ public class CBDatabase {
private class CBSchemaVersionManager implements SQLSchemaVersionManager {
@Override
public int getCurrentSchemaVersion(DBRProgressMonitor monitor, Connection connection, String schemaName) throws DBException, SQLException {
public int getCurrentSchemaVersion(DBRProgressMonitor monitor, Connection connection, String schemaName)
throws DBException, SQLException {
// Check and update schema
try {
int version = CommonUtils.toInt(JDBCUtils.executeQuery(connection,
@@ -386,7 +396,8 @@ public class CBDatabase {
if (updateCount <= 0) {
JDBCUtils.executeSQL(
connection,
normalizeTableNames("INSERT INTO {table_prefix}CB_SCHEMA_INFO (VERSION,UPDATE_TIME) VALUES(?,CURRENT_TIMESTAMP)"),
normalizeTableNames(
"INSERT INTO {table_prefix}CB_SCHEMA_INFO (VERSION,UPDATE_TIME) VALUES(?,CURRENT_TIMESTAMP)"),
version
);
}
@@ -394,7 +405,8 @@ public class CBDatabase {
@Override
//TODO move out
public void fillInitialSchemaData(DBRProgressMonitor monitor, Connection connection) throws DBException, SQLException {
public void fillInitialSchemaData(DBRProgressMonitor monitor, Connection connection)
throws DBException, SQLException {
// Set exclusive connection. Otherwise security controller will open a new one and won't see new schema objects.
exclusiveConnection = new DelegatingConnection<Connection>(connection) {
@Override
@@ -426,7 +438,10 @@ public class CBDatabase {
if (!CommonUtils.isEmpty(initialTeams)) {
// Create teams
for (SMTeam team : initialTeams) {
adminSecurityController.createTeam(team.getTeamId(), team.getName(), team.getDescription(), adminName);
adminSecurityController.createTeam(team.getTeamId(),
team.getName(),
team.getDescription(),
adminName);
if (!application.isMultiNode()) {
adminSecurityController.setSubjectPermissions(
team.getTeamId(),
@@ -451,9 +466,18 @@ public class CBDatabase {
// Persistence
private void validateInstancePersistentState(Connection connection) throws IOException, SQLException {
private void validateInstancePersistentState(Connection connection) throws IOException, SQLException, DBException {
try (JDBCTransaction txn = new JDBCTransaction(connection)) {
checkInstanceRecord(connection);
var defaultTeamId = application.getAppConfiguration().getDefaultUserTeam();
if (CommonUtils.isNotEmpty(defaultTeamId)) {
var team = adminSecurityController.findTeam(defaultTeamId);
if (team == null) {
log.warn("Default users team not found, create :" + defaultTeamId);
adminSecurityController.createTeam(defaultTeamId, defaultTeamId, null,
ApplicationRegistry.getInstance().getApplication().getName());
}
}
txn.commit();
}
}
@@ -475,7 +499,8 @@ public class CBDatabase {
String versionName = CommonUtils.truncateString(GeneralUtils.getProductVersion().toString(), 32);
boolean hasInstanceRecord = JDBCUtils.queryString(connection,
normalizeTableNames("SELECT HOST_NAME FROM {table_prefix}CB_INSTANCE WHERE INSTANCE_ID=?"), instanceId) != null;
normalizeTableNames("SELECT HOST_NAME FROM {table_prefix}CB_INSTANCE WHERE INSTANCE_ID=?"),
instanceId) != null;
if (!hasInstanceRecord) {
JDBCUtils.executeSQL(
connection,
@@ -511,7 +536,8 @@ public class CBDatabase {
}
try (PreparedStatement dbStat = connection.prepareStatement(
normalizeTableNames("INSERT INTO {table_prefix}CB_INSTANCE_DETAILS(INSTANCE_ID,FIELD_NAME,FIELD_VALUE) VALUES(?,?,?)"))
normalizeTableNames(
"INSERT INTO {table_prefix}CB_INSTANCE_DETAILS(INSTANCE_ID,FIELD_NAME,FIELD_VALUE) VALUES(?,?,?)"))
) {
dbStat.setString(1, instanceId);
for (Map.Entry<String, String> ide : instanceDetails.entrySet()) {
@@ -552,5 +578,5 @@ public class CBDatabase {
public SQLDialect getDialect() {
return dialect;
}
}
@@ -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.6.qualifier
Bundle-Release-Date: 20240205
Bundle-Version: 1.0.7.qualifier
Bundle-Release-Date: 20240219
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.6-SNAPSHOT</version>
<version>1.0.7-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.4.3</version>
<version>3.3.2</version>
</dependency>
</dependencies>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.114.qualifier"
version="1.0.115.qualifier"
provider-name="DBeaver Corp">
<plugin id="io.cloudbeaver.resources.drivers.base" download-size="0" install-size="0" version="0.0.0"/>
@@ -9,6 +9,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ce.drivers.feature</artifactId>
<version>1.0.114-SNAPSHOT</version>
<version>1.0.115-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="23.3.4.qualifier"
version="23.3.5.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.product.ce">
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
<version>23.3.4-SNAPSHOT</version>
<version>23.3.5-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="23.3.4.qualifier"
version="23.3.5.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>23.3.4-SNAPSHOT</version>
<version>23.3.5-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.44.qualifier"
version="1.0.45.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.44-SNAPSHOT</version>
<version>1.0.45-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<dbeaver-product>CloudBeaver CE</dbeaver-product>
<dbeaver-version>23.3.4</dbeaver-version>
<dbeaver-version>23.3.5</dbeaver-version>
</properties>
<modules>
@@ -2,7 +2,7 @@
<?pde version="3.5"?>
<product name="CloudBeaver ce Server" uid="io.cloudbeaver.product" id="io.cloudbeaver.product.ce.product"
application="io.cloudbeaver.product.ce.application" version="23.3.4.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="23.3.5.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>23.3.4-SNAPSHOT</version>
<version>23.3.5-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
@@ -4,13 +4,13 @@
teams: [
{
subjectId: "admin",
name: "Admin",
teamName: "Admin",
description: "Administrative access. Has all permissions.",
permissions: [ "admin" ]
},
{
subjectId: "user",
name: "User",
teamName: "User",
description: "All users, including anonymous.",
permissions: [ ]
}
@@ -1,5 +1,5 @@
.space {
composes: theme-background-surface theme-text-on-surface from global;
composes: theme-typography--body2 theme-background-surface theme-text-on-surface from global;
display: flex;
flex-direction: row;
flex: 1;
+1 -1
View File
@@ -5,6 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { importLazyComponent } from '@cloudbeaver/core-utils';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
export const BodyLazy = importLazyComponent(() => import('./Body').then(m => m.Body));
@@ -4,43 +4,36 @@
min-height: 24px;
}
.inputField {
display: none;
display: block;
width: 300px;
&.max {
width: 100%;
}
&.toggled {
display: block;
}
& .input {
& input {
padding-right: 40px !important;
}
}
.iconButton {
position: absolute;
right: 0;
top: 0;
right: 4px;
top: 4px;
margin: 0;
width: 24px;
height: 24px;
border-radius: 2px;
cursor: auto;
&.toggled {
right: 4px;
top: 4px;
}
&.cross {
&.cross svg {
width: 16px;
height: 16px;
top: 8px;
right: 8px;
}
&.cross.manualMode {
right: 32px;
}
}
.toggleMode {
.manualMode {
composes: theme-background-primary theme-text-on-primary from global;
cursor: pointer;
}
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useState } from 'react';
import { useCallback } from 'react';
import { IconButton } from '../IconButton';
import { s } from '../s';
@@ -16,12 +16,13 @@ import filterStyle from './Filter.m.css';
import { InputField } from './InputField';
interface BaseProps {
toggleMode?: boolean;
placeholder?: string;
disabled?: boolean;
disableActions?: boolean;
applyDisabled?: boolean;
max?: boolean;
className?: string;
onToggle?: (status: boolean) => void;
onApply?: (value: string) => void;
onKeyDown?: (event: React.KeyboardEvent<HTMLInputElement>) => void;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
}
@@ -44,19 +45,19 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
state,
name,
value: valueControlled,
toggleMode,
placeholder,
disabled,
disableActions,
applyDisabled,
max,
className,
onApply,
onChange,
onToggle,
onKeyDown,
onClick,
}) {
const styles = useS(filterStyle);
const [inputRef, ref] = useFocus<HTMLInputElement>({});
const [toggled, setToggled] = useState(!toggleMode);
const [inputRef] = useFocus<HTMLInputElement>({});
const filter = useCallback(
(value: string | number, name?: string) => {
@@ -73,55 +74,60 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
[onChange, state],
);
const toggle = useCallback(() => {
if (!toggleMode) {
return;
}
if (toggled) {
filter('');
}
setToggled(!toggled);
if (onToggle) {
onToggle(!toggled);
}
}, [toggleMode, toggled, onToggle, filter]);
useEffect(() => {
if (toggled && toggleMode) {
ref.reference?.focus();
}
}, [toggled, toggleMode, ref.reference]);
let value: any = valueControlled;
if (state && name !== undefined && name in state) {
value = state[name];
}
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === 'Enter' && onApply && !applyDisabled) {
onApply(value);
}
onKeyDown?.(event);
}
function clean() {
filter('', name);
if (onApply) {
onApply('');
}
}
const manualMode = !!onApply;
const edited = !!String(value);
return (
<div className={s(styles, { filterContainer: true }, className)} onClick={onClick}>
<InputField
ref={inputRef}
className={s(styles, { inputField: true, max, toggled })}
className={s(styles, { inputField: true, max })}
placeholder={placeholder}
disabled={disabled}
name={name}
value={value}
onChange={filter}
onKeyDown={onKeyDown}
onKeyDown={handleKeyDown}
/>
{String(value) ? (
{edited && (
<IconButton
className={s(styles, { iconButton: true, cross: true, toggleMode })}
className={s(styles, { iconButton: true, cross: true, manualMode })}
name="cross"
disabled={disabled}
onClick={() => filter('', name)}
disabled={disabled || disableActions}
onClick={clean}
/>
)}
{(!edited || manualMode) && (
<IconButton
className={s(styles, { iconButton: true, manualMode })}
name="search"
disabled={disabled || applyDisabled || disableActions}
onClick={onApply ? () => onApply(value) : undefined}
/>
) : (
<IconButton className={s(styles, { iconButton: true, toggled, toggleMode })} name="search" disabled={disabled} onClick={toggle} />
)}
</div>
);
@@ -162,7 +162,9 @@ export function useResource<
}
if (key === null || propertiesRef.key === null || !propertiesRef.resource.isEqual(key, propertiesRef.key)) {
propertiesRef.key = key;
if (propertiesRef.key !== key) {
propertiesRef.key = key;
}
}
});
@@ -0,0 +1,35 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import React from 'react';
type Props = {
children: React.ReactNode;
};
type State = {
hasError: boolean;
error: any | null;
};
export default class ErrorBoundary extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: any) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <h1>{this.state.error.message}</h1>;
}
return this.props.children;
}
}
@@ -0,0 +1,50 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { render, screen, waitFor } from '@testing-library/react';
import React, { Suspense } from 'react';
import ErrorBoundary from './__custom__mocks__/ErrorBoundaryMock';
import { importLazyComponent } from './importLazyComponent';
describe('importLazyComponent', () => {
const fallback = 'Loading...';
it('should render the lazy component', async () => {
const loadedText = 'Lazy Component';
const mockComponent: React.FC<any> = () => <div>{loadedText}</div>;
const componentImporter = jest.fn(() => Promise.resolve(mockComponent));
const LazyComponent = importLazyComponent(componentImporter);
render(
<Suspense fallback={fallback}>
<LazyComponent />
</Suspense>,
);
expect(screen.getByText(fallback)).toBeTruthy();
await waitFor(() => expect(screen.getByText(loadedText)).toBeTruthy());
expect(componentImporter).toHaveBeenCalled();
});
it('should render the error boundary if rejects with an error', async () => {
const errorText = 'Error';
const componentImporter = jest.fn(() => Promise.reject(new Error(errorText)));
const LazyComponent = importLazyComponent(componentImporter as any);
render(
<ErrorBoundary>
<Suspense fallback={fallback}>
<LazyComponent />
</Suspense>
</ErrorBoundary>,
);
expect(screen.getByText(fallback)).toBeTruthy();
await waitFor(() => expect(screen.getByText(errorText)).toBeTruthy());
expect(componentImporter).toHaveBeenCalled();
});
});
+1
View File
@@ -229,3 +229,4 @@ export * from './useUserData';
export * from './useMergeRefs';
export * from './usePasswordValidation';
export * from './manifest';
export * from './importLazyComponent';
@@ -98,6 +98,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -82,6 +82,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -94,6 +94,7 @@ export default [
['ui_upload', 'Загрузить'],
['ui_import', 'Импортировать'],
['ui_view', 'Смотреть'],
['ui_show_more', 'Показать больше'],
['ui_limit', 'Лимит'],
['ui_file_size', 'Размер файла'],
['ui_processing_synchronization', 'Синхронизация...'],
@@ -95,6 +95,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -85,10 +85,10 @@ export class ServerSettingsService extends Dependency implements ISettingsSource
}
async save() {
const changes = {};
const changes: Record<string, any> = {};
for (const [key, value] of this.changes) {
setByPath(changes, key, value);
changes[key] = value;
}
await this.serverConfigResource.updateProductConfiguration(changes);
@@ -30,4 +30,5 @@ export const defaultProductConfiguration: Record<string, any> = {
plugin_data_spreadsheet_new: {
hidden: false,
},
'sql.proposals.insert.table.alias': true,
};
@@ -1,46 +1,47 @@
mutation getSqlExecuteTaskResults(
$taskId: ID!
) {
result: asyncSqlExecuteResults(
taskId: $taskId
) {
duration
statusMessage
filterText
fullQuery
results {
title
updateRowCount
sourceQuery
dataFormat
resultSet {
id
columns {
dataKind
entityName
fullTypeName
icon
label
maxLength
name
position
precision
required
readOnly
readOnlyStatus
scale
typeName
supportedOperations {
id
expression
argumentCount
}
}
rows
singleEntity
hasMoreData
hasRowIdentifier
}
mutation getSqlExecuteTaskResults($taskId: ID!) {
result: asyncSqlExecuteResults(taskId: $taskId) {
duration
statusMessage
filterText
fullQuery
results {
title
updateRowCount
sourceQuery
dataFormat
resultSet {
id
columns {
dataKind
entityName
fullTypeName
icon
label
maxLength
name
position
precision
required
readOnly
readOnlyStatus
scale
typeName
supportedOperations {
id
expression
argumentCount
}
}
rowsWithMetaData {
data
metaData
}
singleEntity
hasMoreData
hasRowIdentifier
isSupportsDataFilter
hasChildrenCollection
}
}
}
}
@@ -1,33 +1,38 @@
mutation updateResultsDataBatch(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$resultsId: ID!
$updatedRows: [ SQLResultRow! ]
$deletedRows: [ SQLResultRow! ]
$addedRows: [ SQLResultRow! ]
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$resultsId: ID!
$updatedRows: [SQLResultRow!]
$deletedRows: [SQLResultRow!]
$addedRows: [SQLResultRow!]
) {
result: updateResultsDataBatch(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
resultsId: $resultsId
result: updateResultsDataBatch(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
resultsId: $resultsId
updatedRows: $updatedRows
deletedRows: $deletedRows
addedRows: $addedRows
) {
duration
filterText
results {
updateRowCount
resultSet {
id
rows
singleEntity
hasMoreData
hasRowIdentifier
}
updatedRows: $updatedRows
deletedRows: $deletedRows
addedRows: $addedRows
) {
duration
filterText
results {
updateRowCount
resultSet {
id
rowsWithMetaData {
data
metaData
}
singleEntity
hasMoreData
hasRowIdentifier
isSupportsDataFilter
hasChildrenCollection
}
}
}
}
}
@@ -6,4 +6,4 @@
* you may not use this file except in compliance with the License.
*/
export type SettingsScopeType = 'plugin' | 'core';
export type SettingsScopeType = 'plugin' | 'core' | string;
-2
View File
@@ -24,7 +24,6 @@
"fast-deep-equal": "^3.1.3",
"md5": "^2.3.0",
"mobx": "^6.12.0",
"react": "^18.2.0",
"underscore": "^1.13.6",
"uuid": "^9.0.1",
"whatwg-mimetype": "^4.0.0",
@@ -35,7 +34,6 @@
"devDependencies": {
"@types/jest": "^29.5.10",
"@types/md5": "~2.3.5",
"@types/react": "^18.2.42",
"@types/underscore": "^1.11.15",
"@types/uuid": "~9.0.7",
"typescript": "^5.3.2"
@@ -0,0 +1,177 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { OrderedMap } from './OrderedMap';
describe('OrderedMap', () => {
it('should add and get items', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
map.add(Infinity, 'infinity');
map.add(NaN, 'nan');
expect(map.get(1)).toBe('one');
expect(map.get(2)).toBe('two');
expect(map.get(3)).toBe('three');
expect(map.get(Infinity)).toBe('infinity');
expect(map.get(NaN)).toBe('nan');
});
it('should not get not exist item and return undefined', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
expect(map.get(4)).toBeUndefined();
expect(map.get(Infinity)).toBeUndefined();
expect(map.get(NaN)).toBeUndefined();
});
it('should addValue and get items', () => {
const toKey = (v: string) => v.length;
const map = new OrderedMap<number, string>(toKey);
map.addValue('one');
map.addValue('twoo');
map.addValue('three');
expect(map.get(3)).toBe('one');
expect(map.get(4)).toBe('twoo');
expect(map.get(5)).toBe('three');
});
it('should not override with addValue if it already exists', () => {
const toKey = (v: string) => v.length;
const map = new OrderedMap<number, string>(toKey);
map.addValue('one');
map.addValue('twoo');
map.addValue('three');
map.addValue('four');
expect(map.get(4)).toBe('twoo');
});
it('should not addValue if no key fn', () => {
const map = new OrderedMap<number, string>();
expect(() => map.addValue('one')).toThrow();
});
it('should be ordered', () => {
const map = new OrderedMap<number, string>();
map.add(3, 'three');
map.add(1, 'one');
map.add(2, 'two');
expect(map.keys).toEqual([3, 1, 2]);
expect(map.values).toEqual(['three', 'one', 'two']);
});
it('should has items', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
map.add(Infinity, 'infinity');
map.add(NaN, 'nan');
expect(map.has(1)).toBeTruthy();
expect(map.has(2)).toBeTruthy();
expect(map.has(3)).toBeTruthy();
expect(map.has(Infinity)).toBeTruthy();
expect(map.has(NaN)).toBeTruthy();
});
it('should not override items', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(1, 'two');
map.add(Infinity, 'infinity');
map.add(Infinity, 'infinity2');
map.add(NaN, 'nan');
map.add(NaN, 'nan2');
expect(map.get(1)).toBe('one');
expect(map.get(Infinity)).toBe('infinity');
expect(map.get(NaN)).toBe('nan');
});
it('should remove items', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
map.remove(2);
expect(map.get(2)).toBeUndefined();
expect(map.keys).toEqual([1, 3]);
expect(map.values).toEqual(['one', 'three']);
});
it('should not have non-existing items after removal', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
expect(map.has(4)).toBeFalsy();
expect(map.get(4)).toBeUndefined();
map.remove(4);
expect(map.has(4)).toBeFalsy();
expect(map.get(4)).toBeUndefined();
});
it('should remove all items', () => {
const map = new OrderedMap<number, string>();
map.add(1, 'one');
map.add(2, 'two');
map.add(3, 'three');
map.removeAll();
expect(map.keys).toEqual([]);
expect(map.values).toEqual([]);
});
it('should throw bulk update items if no toKey fn', () => {
const map = new OrderedMap<number, string>();
expect(() => map.bulkUpdate(['one', 'two', 'three'])).toThrow();
});
it('should bulk update items', () => {
const toKey = (v: string) => v.length;
const map = new OrderedMap<number, string>(toKey);
map.bulkUpdate(['o', 'tw', 'thr']);
expect(map.keys).toEqual([1, 2, 3]);
expect(map.values).toEqual(['o', 'tw', 'thr']);
});
it('should bulk rewrite items', () => {
const toKey = (v: string) => v.length;
const map = new OrderedMap<number, string>(toKey);
map.bulkUpdate(['o', 'tw', 'thr']);
map.bulkRewrite(['one', 'twoo', 'three']);
expect(map.keys).toEqual([3, 4, 5]);
expect(map.values).toEqual(['one', 'twoo', 'three']);
});
it('should sort items', () => {
const map = new OrderedMap<number, string>();
map.add(3, 'c');
map.add(1, 'a');
map.add(2, 'b');
map.sort((a, b) => (a > b ? 1 : -1));
expect(map.keys).toEqual([1, 2, 3]);
});
});
@@ -0,0 +1,41 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { blobToBase64 } from './blobToBase64';
describe('blobToBase64', () => {
it('converts blob to base64', async () => {
const blob = new Blob(['test'], { type: 'text/plain' });
const base64 = await blobToBase64(blob);
expect(base64).toBe('data:text/plain;base64,dGVzdA==');
});
it('converts blob to base64 with slice', async () => {
const blob = new Blob(['this is a test with longer text']);
const base64 = await blobToBase64(blob, 20);
expect(base64).toBe('data:application/octet-stream;base64,dGhpcyBpcyBhIHRlc3Qgd2l0aCA=');
});
it('calls readAsDataURL', async () => {
const readAsDataURL = jest.fn(blob => Promise.resolve(blob));
Object.defineProperty(global, 'FileReader', {
writable: true,
value: jest.fn().mockImplementation(() => ({
readAsDataURL,
})),
});
jest.useFakeTimers();
const blob = new Blob(['test'], { type: 'text/plain' });
blobToBase64(blob);
expect(readAsDataURL).toHaveBeenCalledWith(blob);
});
});
-1
View File
@@ -8,7 +8,6 @@ export * from './Quadtree/index';
export * from './underscore';
export * from './importLazyComponent';
export * from './base64ToBlob';
export * from './blobToBase64';
export * from './base64ToHex';
@@ -23,6 +23,7 @@ import {
useTranslate,
} from '@cloudbeaver/core-blocks';
import { CachedResourceOffsetPageListKey } from '@cloudbeaver/core-resource';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
import type { ITeamFormProps } from '../ITeamFormProps';
@@ -38,20 +39,33 @@ export const GrantedUsers: TabContainerPanelComponent<ITeamFormProps> = observer
const state = useGrantedUsers(formState.config, formState.mode);
const { selected } = useTab(tabId);
const serverConfigResource = useResource(UserList, ServerConfigResource, undefined, { active: selected });
const isDefaultTeam = formState.config.teamId === serverConfigResource.data?.defaultUserTeam;
const users = useResource(GrantedUsers, UsersResource, CachedResourceOffsetPageListKey(0, 1000).setTarget(UsersResourceFilterKey()), {
active: selected,
active: selected && !isDefaultTeam,
});
const grantedUsers = getComputed(() =>
users.data.filter<AdminUser>((user): user is AdminUser => !!user && state.state.grantedUsers.includes(user.userId)),
);
useAutoLoad(GrantedUsers, state, selected && !state.state.loaded);
useAutoLoad(GrantedUsers, state, selected && !state.state.loaded && !isDefaultTeam);
if (!selected) {
return null;
}
if (isDefaultTeam) {
return (
<ColoredContainer className={s(styles, { box: true })} parent gap vertical>
<Group className={s(styles, { placeholderBox: true })} keepSize large>
<TextPlaceholder>{translate('plugin_authentication_administration_team_default_users_tooltip')}</TextPlaceholder>
</Group>
</ColoredContainer>
);
}
return (
<Loader className={s(styles, { loader: true })} state={[state.state]}>
{() => (
@@ -174,7 +174,7 @@ export class UserFormInfoPart extends FormPart<IUserFormInfoState, IUserFormStat
let grantedTeams: string[] = [];
if (this.state.userId) {
grantedTeams = this.usersResource.get(this.state.userId)?.grantedTeams ?? [];
grantedTeams = toJS(this.usersResource.get(this.state.userId)?.grantedTeams ?? []);
}
if (isArraysEqual(this.state.teams, grantedTeams)) {
@@ -10,6 +10,7 @@ import { observer } from 'mobx-react-lite';
import { compareTeams, TeamsResource } from '@cloudbeaver/core-authentication';
import { FieldCheckbox, Group, GroupTitle, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { isDefined } from '@cloudbeaver/core-utils';
import type { UserFormProps } from '../AdministrationUserFormService';
@@ -23,27 +24,33 @@ interface Props extends UserFormProps {
export const UserFormInfoTeams = observer<Props>(function UserFormInfoTeams({ formState, tabState, tabSelected, disabled }) {
const translate = useTranslate();
const serverConfigResource = useResource(UserFormInfoTeams, ServerConfigResource, undefined);
const teamsLoader = useResource(UserFormInfoTeams, TeamsResource, CachedMapAllKey, { active: tabSelected });
const teams = teamsLoader.data.filter(isDefined).sort(compareTeams);
const defaultTeam = serverConfigResource.data?.defaultUserTeam;
return (
<>
<GroupTitle>{translate('authentication_user_team')}</GroupTitle>
<Group boxNoOverflow gap dense>
{teams.map(team => {
const isDefault = team.teamId === defaultTeam;
const label = `${team.teamId}${team.teamName && team.teamName !== team.teamId ? ' (' + team.teamName + ')' : ''}`;
const tooltip = `${label}${team.description ? '\n' + team.description : ''}`;
return (
<FieldCheckbox
key={team.teamId}
id={`${formState.id}_${team.teamId}`}
title={tooltip}
label={label}
name="teams"
state={tabState.state}
value={team.teamId}
caption={isDefault ? translate('plugin_authentication_administration_user_team_default_readonly_tooltip') : undefined}
readOnly={isDefault}
disabled={disabled}
>
{label}
</FieldCheckbox>
/>
);
})}
</Group>
@@ -129,4 +129,7 @@ export default [
['administration_teams_team_granted_connections_tab_title', 'Connections'],
['administration_teams_team_granted_connections_search_placeholder', 'Search for connection name...'],
['administration_teams_team_granted_connections_empty', 'No available connections'],
['plugin_authentication_administration_user_team_default_readonly_tooltip', "Default team. Can't be revoked"],
['plugin_authentication_administration_team_default_users_tooltip', 'Default team. Contains all users'],
];
@@ -74,4 +74,7 @@ export default [
'administration_configuration_wizard_configuration_authentication_description',
"Permetti agli utenti di autenticarsi. In alternativa solo l'accesso anonimo sarà attivo",
],
['plugin_authentication_administration_user_team_default_readonly_tooltip', "Default team. Can't be revoked"],
['plugin_authentication_administration_team_default_users_tooltip', 'Default team. Contains all users'],
];
@@ -134,4 +134,7 @@ export default [
['administration_teams_team_granted_connections_tab_title', 'Подключения'],
['administration_teams_team_granted_connections_search_placeholder', 'Поиск по названию подключения...'],
['administration_teams_team_granted_connections_empty', 'Нет доступных подключений'],
['plugin_authentication_administration_user_team_default_readonly_tooltip', 'Команда по умолчанию. Не может быть отозвана'],
['plugin_authentication_administration_team_default_users_tooltip', 'Команда по умолчанию. Содержит всех пользователей'],
];
@@ -111,4 +111,7 @@ export default [
['administration_teams_team_granted_connections_tab_title', '连接'],
['administration_teams_team_granted_connections_search_placeholder', '搜索连接名称...'],
['administration_teams_team_granted_connections_empty', '没有可用连接'],
['plugin_authentication_administration_user_team_default_readonly_tooltip', "Default team. Can't be revoked"],
['plugin_authentication_administration_team_default_users_tooltip', 'Default team. Contains all users'],
];
@@ -5,6 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { importLazyComponent } from '@cloudbeaver/core-utils';
import { importLazyComponent } from '@cloudbeaver/core-blocks';
export const EditorLoader = importLazyComponent(() => import('./Editor').then(m => m.Editor));
@@ -22,6 +22,7 @@ export interface IReactCodeMirrorProps extends React.PropsWithChildren {
getValue?: () => string;
extensions?: Map<Compartment, Extension>;
readonly?: boolean;
disableCopy?: boolean;
autoFocus?: boolean;
onChange?: (value: string, update: ViewUpdate) => void;
onCursorChange?: (selection: SelectionRange, update: ViewUpdate) => void;

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