mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f01d49f5f | ||
|
|
cb897343bd | ||
|
|
eea72bd467 | ||
|
|
b62b8d178d | ||
|
|
9828b1de1e | ||
|
|
325cbef1d8 | ||
|
|
1e13e65205 | ||
|
|
426ab15bfc | ||
|
|
898e7d9b9d | ||
|
|
1d497fe14f | ||
|
|
3965f16330 | ||
|
|
3891eed4a9 | ||
|
|
f980ee80b2 | ||
|
|
1816016cf3 | ||
|
|
9e051d5158 | ||
|
|
7437b9e5eb | ||
|
|
d96b9e94ad | ||
|
|
7eba02ac1f | ||
|
|
e285ad0279 | ||
|
|
6b2f314383 | ||
|
|
09e5141e37 | ||
|
|
0c03d201a8 | ||
|
|
d96e0b812d | ||
|
|
98d7d1f0d5 | ||
|
|
590830cbb5 | ||
|
|
4d04f4b2ee | ||
|
|
8cdfb12ecc | ||
|
|
30e0bad784 | ||
|
|
f8e74f926c | ||
|
|
b7ab8b13d6 |
@@ -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,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: [ ]
|
||||
}
|
||||
|
||||
@@ -27,5 +27,6 @@
|
||||
},
|
||||
plugin_data_export: {
|
||||
disabled: false
|
||||
}
|
||||
},
|
||||
sql.proposals.insert.table.alias: true
|
||||
}
|
||||
|
||||
@@ -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: .
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+11
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-4
@@ -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();
|
||||
|
||||
+197
@@ -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();
|
||||
}
|
||||
}
|
||||
+28
-3
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+60
-31
@@ -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()];
|
||||
|
||||
|
||||
+30
-12
@@ -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;
|
||||
|
||||
+34
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -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>
|
||||
|
||||
+9
@@ -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>
|
||||
|
||||
+9
@@ -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>
|
||||
|
||||
+62
-7
@@ -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);
|
||||
}
|
||||
|
||||
+38
-12
@@ -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: .
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ export * from './Quadtree/index';
|
||||
|
||||
export * from './underscore';
|
||||
|
||||
export * from './importLazyComponent';
|
||||
export * from './base64ToBlob';
|
||||
export * from './blobToBase64';
|
||||
export * from './base64ToHex';
|
||||
|
||||
+16
-2
@@ -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]}>
|
||||
{() => (
|
||||
|
||||
+1
-1
@@ -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
-3
@@ -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
Reference in New Issue
Block a user