Compare commits

...
22 Commits
Author SHA1 Message Date
Alexander Skoblikovandnaumov 6416486513 Cb 4460 shared query history for the team (#2598)
* CB-5067 team supervisor

* CB-4460 extended team members info api

* CB-5118 support supervisor role

* CB-4460 api fix

* CB-4460 change role name

* CB-4460 gql fix

* CB-4460 fix unexpected team role modification

* CB-4460 fix unexpected team role modification

* CB-4460 fix unexpected team role modification

* CB-4460 fix anonymous preferences

* CB-4460 adjust compare fn


---------

Co-authored-by: naumov <iamemptyhuh@gmail.com>
2024-05-14 17:30:26 +00:00
AinurandDenisSinelnikov 6061ec340c CB-5127 web application instance id fix (#2603)
Co-authored-by: DenisSinelnikov <142215442+DenisSinelnikov@users.noreply.github.com>
2024-05-14 19:29:09 +02:00
b40646e69b Cb 4931 auth dialog focus (#2602)
* CB-4931 focus first child of the auth form

* CB-4931 focus dialog from inner component

---------

Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-05-14 19:27:09 +02:00
serge-rider 003a89be57 24.0.5 version bump 2024-05-14 18:37:03 +02:00
DenisSinelnikov bbd296f197 CB-5007. Fixed import to mongo (#2612)
* CB-5007. Fixed import to mongo

* CB-5007. Fixed checkstyle
2024-05-14 16:18:08 +00:00
AlexeyandEvgenia Bezborodova 39bfc54ca5 CB-4698 fix: limit grid inline editor (#2608)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-05-14 19:03:52 +03:00
bac47d20ce CB-5070 refactor: forms design (#2607)
* CB-5070 refactor: forms design

* Merge remote-tracking branch 'origin/devel' into refactor/cb-5070/forms-design

* CB-5070 fix: html semantics

* CB-5070 fix: table header styles

* CB-5070 fix: table header styles

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-05-14 19:03:11 +03:00
alexandDaria Marutkina 5fce0a544b CB-5085 make errors focusable (#2585)
* CB-5085 make errors focusable

* CB-5085 add lazy import

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-05-14 16:36:24 +03:00
833dae58ce CB-5060 add beta features config param (#2599)
* CB-5109 add beta features config param

* CB-5109 rename param

* CB-5060 feat: disable beta features

* CB-5109 add param to config file

---------

Co-authored-by: Alexey <wrouds@gmail.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-05-14 09:31:24 +00:00
Alexander SkoblikovandEvgenia Bezborodova 12bc21257d CB-5128 do not store jetty sessions cache in files (#2600)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-05-14 09:42:42 +02:00
d280171b97 CB-4978 feat: handle app update (#2579)
* CB-4978 feat: handle app update

* CB-4978 fix: app loading status info

* feat: async module loading and preload api

* chore: disable SW in dev env

* CB-4978 feat: improve precaching

* CB-4978 fix: split style chunks in two file

* CB-4978 fix: App rebinding

* CB-4978 fix: styles specificity

* CB-4978 fix: app loading

* CB-4978 fix: styles specificity

* CB-4978 fix: flickering bad positioned loader

* CB-4978 fix: styles specificity

* CB-4978 fix: table presentation scrolls

* CB-4978 chore: add extra error logging

* CB-4978 chore: add extra error logging

* CB-4978 fix: toolbar scrolling

* CB-4978 fix: log viewer empty message

* CB-4978 fix: cell styles

* CB-4978 fix: tabs styles

* CB-4978 fix: tests

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-05-14 09:16:46 +03:00
alexandDaria Marutkina c8ec5716b8 CB-4931 make export and import dialog focusable (#2584)
* CB-4931 make export and import dialog focusable

* CB-4931 always focus the dialog

---------

Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-05-10 16:51:08 +03:00
serge-rider 7045575d95 dbeaver/pro#2800 Code style 2024-05-10 14:57:48 +02:00
AlexeyandDaria Marutkina bdea460218 CB-4837 fix: show in value panel menu button (#2596)
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
2024-05-09 13:19:08 +03:00
674c522d55 Cb 5076 remove reshadow leftovers (#2582)
* CB-3960 removes unused SlideBox styles

* CB-3960 makes menu panel reshadowless

* CB-3960 makes menu bar small item reshadowless

* CB-3960 removes reshadow from loaders

* CB-5076 some reshadow

* CB-5076 makes auto refresh reshadowless

* CB-5076 removes reshadow from result set grouping presentation

* CB-5076 makes data grid reshadowless

* CB-5076 makes object viewer table reshadowless

* CB-5076 makes value panel GIS presentation reshadowless

* CB-5076 removes reshadow's all dependencies

* CB-5076 removes ComponentStyles from tsx files

* CB-5076 fixes shortcut dialog titles paddings

* CB-5076 federated auth providers has more space for providers list container

* CB-5076 removes unneeded global from base scss

* CB-5076 import base styles as regular styles (not moduled)

* CB-5076 fix: styles

* CB-5076 chore: add packageManager

---------

Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
Co-authored-by: Aleksei Potsetsuev <wrouds@gmail.com>
2024-05-09 13:17:13 +03:00
Alexander Skoblikov d0933b2b17 CB-5050 query translator unit tests (#2590)
* CB-5050 query translator unit tests

* CB-5050 mysql UUID column

* CB-5050 update mysql dialect tests
2024-05-08 19:03:11 +00:00
alexandEvgenia Bezborodova 56143132d6 CB-5077 adjust contrast ratio (#2583)
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
2024-05-08 13:56:02 +03:00
Alexander Skoblikov 639f42fa1c CB-4922 bump duck db driver version (#2594) 2024-05-08 09:32:17 +00:00
Alexander Skoblikov c0bbe7fdc5 remove gson map usage (#2593) 2024-05-07 13:29:01 +00:00
Daria Marutkina 64103bc369 Merge pull request #2592 from dbeaver/dariamarutkina-patch-1
CB_2404 chore: update README
2024-05-07 09:49:58 +02:00
Daria Marutkina 87e7f774e9 CB_2404 chore: update README 2024-05-07 09:49:46 +02:00
Ainurandkseniaguzeeva 00cb84c71d CB-5029 close all sessions with event (#2577)
* CB-5029 close all sessions with event

* CB-5029 fixes after review

* CB-5029 remove mac address from instance

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
2024-04-30 10:38:29 +00:00
382 changed files with 4020 additions and 4908 deletions
+8 -33
View File
@@ -22,6 +22,14 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
## Changelog
### 24.0.4. 2024-05-06
- Added the ability to stop the process of file upload in the table;
- Row count calculation in the grid can be cancelled for Data Editor and SQL Editor;
- Added the ability to set null values for BLOB and GIS data via the cell's context menu in the table;
- Oracle driver has been updated to version 23.2.0.0.0;
- SQLite driver has been updated to version 3.44.1.0;
- Different bug fixes and enhancements have been made.
### 24.0.3. 2024-04-22
- Unauthorized access vulnerability was fixed;
- Added the ability for users to configure personal settings for the interface, SQL editor, and data viewer through the settings panel;
@@ -43,36 +51,3 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io
- Auto-commit transfers all changes that you make immediately to the database;
- Manual commit requires your confirmation before committing a change to the database or rolling it back.
- Different bug fixes and enhancements have been made.
### 24.0.0. 2024-03-04
- Changes since 23.3.0:
- Main updates:
- Have enhanced security for unauthorised access;
- Have added a password policy for the local authorization. Password parameters can be set in the configuration file;
- Have added validation for mandatory fields in all forms to create and edit entities;
- Have added an ability to edit the default preferences of the following parts: interface, tools and data viewer in the settings panel in the administrative part;
- Performance:
- Have improved the performance of the server.
- Administration:
- Have added the ability to configure reverse proxy header name and redirect URL at logout. Now the administrator can configure all settings. The delimiter symbol for the reverse proxy team headers is configurable;
- Preferences:
- Have added the ability in the admin panel to customise copying in the Tables and Values panel for all users except the administrator;
- Have added the ability to disable alias autocomplete for all users.
- Access Management:
- A default user group was added to the product. This group includes all users which could not be deleted.
- Connections:
- The 'Keep alive' option has been added to the connection settings to keep the connection active even in case of inactivity.
- Data Editor:
- Have added the ability to view decoded binary-type data in the Value panel;
- Text wrap is activated by default for texts and BLOBs in the Values panel for better visibility. The user can switch to the one-line mode using a button on the toolbar;
- Have added the ability to display full text for a string data type in the Value panel;
- Have added the ability to display the OpenStreetMap layer with the coordinate system ESPG 4326.
- Resource management:
- Read-only scripts now have a padlock icon.
- Databases:
- The DuckDB driver has been added;
- Updated PostgreSQL driver to 42.5.2;
- ClickHouse Legacy driver has been removed to prevent potential vulnerability issues;
- Updated MariaDB driver to version 3.3.2.
- Different bug fixes and enhancements have been made.
@@ -94,6 +94,9 @@
"h2:h2_embedded",
"h2:h2_embedded_v2",
"clickhouse:yandex_clickhouse"
],
disabledBetaFeatures: [
]
}
@@ -89,6 +89,9 @@
"h2:h2_embedded",
"h2:h2_embedded_v2",
"clickhouse:yandex_clickhouse"
],
disabledBetaFeatures: [
]
}
@@ -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.52.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.53.qualifier
Bundle-Release-Date: 20240520
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.model</artifactId>
<version>1.0.52-SNAPSHOT</version>
<version>1.0.53-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -33,11 +33,14 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
protected boolean resourceManagerEnabled;
protected boolean showReadOnlyConnectionInfo;
protected String[] enabledFeatures;
protected String[] disabledBetaFeatures;
public BaseWebAppConfiguration() {
this.plugins = new LinkedHashMap<>();
this.resourceManagerEnabled = true;
this.enabledFeatures = null;
this.disabledBetaFeatures = new String[0];
this.showReadOnlyConnectionInfo = false;
}
@@ -46,6 +49,7 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
this.defaultUserTeam = src.defaultUserTeam;
this.resourceManagerEnabled = src.resourceManagerEnabled;
this.enabledFeatures = src.enabledFeatures;
this.disabledBetaFeatures = src.disabledBetaFeatures;
this.showReadOnlyConnectionInfo = src.showReadOnlyConnectionInfo;
}
@@ -104,4 +108,8 @@ public abstract class BaseWebAppConfiguration implements WebAppConfiguration {
public boolean isShowReadOnlyConnectionInfo() {
return showReadOnlyConnectionInfo;
}
public String[] getDisabledBetaFeatures() {
return disabledBetaFeatures;
}
}
@@ -35,12 +35,12 @@ import org.jkiss.dbeaver.model.app.DBPWorkspace;
import org.jkiss.dbeaver.model.auth.SMCredentialsProvider;
import org.jkiss.dbeaver.model.auth.SMSessionContext;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import org.jkiss.dbeaver.model.impl.app.ApplicationRegistry;
import org.jkiss.dbeaver.model.rm.RMController;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.secret.DBSSecretController;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import org.jkiss.dbeaver.registry.BaseApplicationImpl;
import org.jkiss.dbeaver.registry.BaseWorkspaceImpl;
import org.jkiss.dbeaver.runtime.IVariableResolver;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.dbeaver.utils.RuntimeUtils;
@@ -64,6 +64,8 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
private static final Log log = Log.getLog(BaseWebApplication.class);
private String instanceId;
@NotNull
@Override
public DBPWorkspace createWorkspace(@NotNull DBPPlatform platform, @NotNull IWorkspace eclipseWorkspace) {
@@ -242,14 +244,17 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements
protected abstract void startServer() throws DBException;
@Override
public String getApplicationInstanceId() throws DBException {
try {
byte[] macAddress = RuntimeUtils.getLocalMacAddress();
String appId = ApplicationRegistry.getInstance().getApplication().getId();
return appId + "_" + CommonUtils.toHexString(macAddress) + getServerPort();
} catch (Exception e) {
throw new DBException("Error during generation instance id generation", e);
public synchronized String getApplicationInstanceId() throws DBException {
if (instanceId == null) {
try {
byte[] macAddress = RuntimeUtils.getLocalMacAddress();
// workspace id from is read from property file
instanceId = BaseWorkspaceImpl.readWorkspaceIdProperty() + "_" + CommonUtils.toHexString(macAddress);
} catch (Exception e) {
throw new DBException("Error during generation instance id generation", e);
}
}
return instanceId;
}
public String getApplicationId() {
@@ -70,6 +70,7 @@ public class WebUser {
return Collections.emptyMap();
}
@NotNull
public String[] getTeams() {
return user.getUserTeams();
}
@@ -31,4 +31,8 @@ public class WebGlobalWorkspace extends EclipseWorkspaceImpl {
super(platform, eclipseWorkspace);
}
@Override
protected String initWorkspaceId() {
return readWorkspaceIdProperty();
}
}
@@ -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: 24.0.4.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 24.0.5.qualifier
Bundle-Release-Date: 20240520
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.product.ce</artifactId>
<version>24.0.4-SNAPSHOT</version>
<version>24.0.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.97.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.98.qualifier
Bundle-Release-Date: 20240520
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.97-SNAPSHOT</version>
<version>1.0.98-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: 24.0.4.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 24.0.5.qualifier
Bundle-Release-Date: 20240520
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server</artifactId>
<version>24.0.4-SNAPSHOT</version>
<version>24.0.5-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -153,6 +153,7 @@ type ServerConfig {
distributed: Boolean!
enabledFeatures: [ID!]!
disabledBetaFeatures: [ID!] @since(version: "24.0.5")
enabledAuthProviders: [ID!]!
supportedLanguages: [ ServerLanguage! ]!
services: [ WebServiceConfig ]
@@ -21,6 +21,7 @@ import io.cloudbeaver.registry.WebServiceRegistry;
import io.cloudbeaver.server.CBApplication;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.security.PasswordPolicyConfiguration;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
import org.jkiss.dbeaver.registry.language.PlatformLanguageDescriptor;
@@ -155,6 +156,12 @@ public class WebServerConfig {
return application.getAppConfiguration().getEnabledFeatures();
}
@Property
@Nullable
public String[] getDisabledBetaFeatures() {
return application.getAppConfiguration().getDisabledBetaFeatures();
}
@Property
public String[] getEnabledAuthProviders() {
return application.getAppConfiguration().getEnabledAuthProviders();
@@ -48,7 +48,6 @@ import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.websocket.event.WSEventController;
import org.jkiss.dbeaver.model.websocket.event.WSServerConfigurationChangedEvent;
import org.jkiss.dbeaver.registry.BaseApplicationImpl;
import org.jkiss.dbeaver.registry.BaseWorkspaceImpl;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.runtime.ui.DBPPlatformUI;
import org.jkiss.dbeaver.utils.GeneralUtils;
@@ -322,8 +321,6 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
System.setSecurityManager(new SecurityManager());
}
saveWorkspaceId();
eventController.scheduleCheckJob();
runWebServer();
@@ -333,16 +330,6 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
return;
}
private void saveWorkspaceId() {
final Path metadataFolder = GeneralUtils.getMetadataFolder();
Properties props = BaseWorkspaceImpl.readWorkspaceInfo(metadataFolder);
String workspaceIdProperty = "workspace-id";
if (!props.contains(workspaceIdProperty)) {
props.setProperty(workspaceIdProperty, CBPlatform.getInstance().getWorkspace().getWorkspaceId());
BaseWorkspaceImpl.writeWorkspaceInfo(metadataFolder, props);
}
}
protected void initializeAdditionalConfiguration() {
}
@@ -698,6 +685,10 @@ public abstract class CBApplication<T extends CBServerConfig> extends BaseWebApp
return List.of();
}
public List<String> getAvailableTeamRoles() {
return List.of();
}
@Override
public WSEventController getEventController() {
return eventController;
@@ -17,6 +17,7 @@
package io.cloudbeaver.server.events;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.session.WebSessionManager;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.websocket.WSEventHandler;
import org.jkiss.dbeaver.model.websocket.event.WSAbstractEvent;
@@ -31,15 +32,20 @@ public class WSUserEventHandler<EVENT extends WSAbstractEvent> implements WSEven
if (eventType == null) {
return;
}
WebSessionManager sessionManager = CBPlatform.getInstance().getSessionManager();
switch (eventType) {
case CLOSE_USER_SESSIONS:
if (event instanceof WSUserCloseSessionsEvent closeSessionsEvent) {
CBPlatform.getInstance().getSessionManager().closeSessions(closeSessionsEvent.getSessionIds());
if (closeSessionsEvent.getSessionIds().isEmpty()) {
sessionManager.closeAllSessions();
} else {
sessionManager.closeSessions(closeSessionsEvent.getSessionIds());
}
}
break;
case USER_DELETED:
if (event instanceof WSUserDeletedEvent userDeletedEvent) {
CBPlatform.getInstance().getSessionManager().closeUserSession(userDeletedEvent.getUserId());
sessionManager.closeUserSession(userDeletedEvent.getUserId());
}
break;
default:
@@ -28,7 +28,7 @@ import io.cloudbeaver.service.DBWServiceBindingServlet;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.DefaultSessionIdManager;
import org.eclipse.jetty.server.session.FileSessionDataStore;
import org.eclipse.jetty.server.session.NullSessionDataStore;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
@@ -41,11 +41,8 @@ import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -189,17 +186,6 @@ public class CBJettyServer {
@NotNull ServletContextHandler servletContextHandler
) {
// Init sessions persistence
Path metadataFolder = GeneralUtils.getMetadataFolder(DBWorkbench.getPlatform().getWorkspace().getAbsolutePath());
Path sessionCacheFolder = metadataFolder.resolve(SESSION_CACHE_DIR);
if (!Files.exists(sessionCacheFolder)) {
try {
Files.createDirectories(sessionCacheFolder);
} catch (IOException e) {
log.error("Can't create http session cache directory '" + sessionCacheFolder.toAbsolutePath() + "'", e);
return;
}
}
SessionHandler sessionHandler = new SessionHandler();
var maxIdleTime = application.getMaxSessionIdleTime();
int intMaxIdleSeconds;
@@ -212,10 +198,9 @@ public class CBJettyServer {
sessionHandler.setMaxInactiveInterval(intMaxIdleSeconds);
DefaultSessionCache sessionCache = new DefaultSessionCache(sessionHandler);
FileSessionDataStore sessionStore = new FileSessionDataStore();
sessionStore.setStoreDir(sessionCacheFolder.toFile());
sessionCache.setSessionDataStore(sessionStore);
sessionCache.setSessionDataStore(new NullSessionDataStore());
sessionHandler.setSessionCache(sessionCache);
servletContextHandler.setSessionHandler(sessionHandler);
}
@@ -267,8 +267,7 @@ public class WebServiceCore implements DBWServiceCore {
public boolean closeSession(HttpServletRequest request) throws DBWebException {
try {
var baseWebSession = CBPlatform.getInstance().getSessionManager().closeSession(request);
if (baseWebSession instanceof WebSession) {
var webSession = (WebSession) baseWebSession;
if (baseWebSession instanceof WebSession webSession) {
for (WebSessionHandlerDescriptor hd : WebHandlerRegistry.getInstance().getSessionHandlers()) {
try {
hd.getInstance().handleSessionClose(webSession);
@@ -711,15 +710,12 @@ public class WebServiceCore implements DBWServiceCore {
);
if (connectionConfig.getSelectedSecretId() != null) {
try {
DBSSecretValue secretValue = dataSource.listSharedCredentials()
dataSource.listSharedCredentials()
.stream()
.filter(secret -> connectionConfig.getSelectedSecretId().equals(secret.getSubjectId()))
.findFirst()
.orElse(null);
.ifPresent(testDataSource::setSelectedSharedCredentials);
if (secretValue != null) {
testDataSource.setSelectedSharedCredentials(secretValue);
}
} catch (DBException e) {
throw new DBWebException("Failed to load secret value: " + connectionConfig.getSelectedSecretId());
}
@@ -741,7 +737,7 @@ public class WebServiceCore implements DBWServiceCore {
testDataSource.getConnectionConfiguration());
testDataSource.setSavePassword(true); // We need for test to avoid password callback
if (DataSourceDescriptor.class.isAssignableFrom(testDataSource.getClass())) {
((DataSourceDescriptor) testDataSource).setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
testDataSource.setAccessCheckRequired(!webSession.hasPermission(DBWConstants.PERMISSION_ADMIN));
}
try {
ConnectionTestJob ct = new ConnectionTestJob(testDataSource, param -> {
@@ -776,8 +772,7 @@ public class WebServiceCore implements DBWServiceCore {
}
try {
DBWNetworkHandler handler = handlerDescriptor.createHandler(DBWNetworkHandler.class);
if (handler instanceof DBWTunnel) {
DBWTunnel tunnel = (DBWTunnel) handler;
if (handler instanceof DBWTunnel tunnel) {
DBPConnectionConfiguration connectionConfig = new DBPConnectionConfiguration();
connectionConfig.setHostName(DBConstants.HOST_LOCALHOST);
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties()
@@ -875,7 +870,6 @@ public class WebServiceCore implements DBWServiceCore {
@Nullable String parentPath,
@NotNull String folderName
) throws DBWebException {
DBRProgressMonitor monitor = session.getProgressMonitor();
WebConnectionFolderUtils.validateConnectionFolder(folderName);
session.addInfoMessage("Create new folder");
WebConnectionFolderInfo parentNode = null;
@@ -350,9 +350,9 @@ public class WebSessionManager {
synchronized (sessionMap) {
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
var session = iterator.next();
if (CommonUtils.equalObjects(session.getUserContext().getUserId(), userId)) {
iterator.remove();
session.close();
if (CommonUtils.equalObjects(session.getUserContext().getUserId(), userId)) {
iterator.remove();
session.close();
}
}
}
@@ -362,13 +362,24 @@ public class WebSessionManager {
synchronized (sessionMap) {
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
var session = iterator.next();
for (String smSessionId : smSessionsId) {
if (CommonUtils.equalObjects(session.getUserContext().getSmSessionId(), smSessionId)) {
iterator.remove();
session.close(false);
}
if (smSessionsId.contains(session.getUserContext().getSmSessionId())) {
iterator.remove();
session.close(false);
}
}
}
}
/**
* Closes all sessions in session manager.
*/
public void closeAllSessions() {
synchronized (sessionMap) {
for (Iterator<BaseWebSession> iterator = sessionMap.values().iterator(); iterator.hasNext(); ) {
var session = iterator.next();
iterator.remove();
session.close(false);
}
}
}
}
@@ -16,7 +16,6 @@
*/
package io.cloudbeaver.service.sql;
import com.google.gson.internal.LinkedTreeMap;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.session.WebSession;
@@ -667,7 +666,7 @@ public class WebSQLProcessor implements WebSessionProvider {
for (int i = 0; i < allAttributes.length; i++) {
if (addedValues[i] != null) {
Object realCellValue;
if (addedValues[i] instanceof LinkedTreeMap variables) {
if (addedValues[i] instanceof Map<?, ?> variables) {
realCellValue = setCellRowValue(variables, webSession, session, allAttributes[i], withoutExecution);
} else {
realCellValue = convertInputCellValue(session, allAttributes[i],
@@ -1140,8 +1139,8 @@ public class WebSQLProcessor implements WebSessionProvider {
}
private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession dbcSession, DBDAttributeBinding allAttributes, boolean withoutExecution) {
if (cellRow instanceof LinkedTreeMap) {
LinkedTreeMap<String, Object> variables = (LinkedTreeMap<String, Object>) cellRow;
if (cellRow instanceof Map<?, ?>) {
Map<String, Object> variables = (Map<String, Object>) cellRow;
if (variables.get(FILE_ID) != null) {
Path path = CBPlatform.getInstance()
.getTempFolder(webSession.getProgressMonitor(), TEMP_FILE_FOLDER)
@@ -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.96.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.97.qualifier
Bundle-Release-Date: 20240520
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.96-SNAPSHOT</version>
<version>1.0.97-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -11,6 +11,11 @@ type AdminConnectionGrantInfo {
subjectType: AdminSubjectType!
}
type AdminUserTeamGrantInfo @since(version: "24.0.5"){
userId: ID!
teamRole: String
}
type AdminObjectPermissions {
objectId: ID!
permissions: [String!]!
@@ -53,6 +58,7 @@ type AdminTeamInfo {
metaParameters: Object!
grantedUsers: [ID!]!
grantedUsersInfo: [AdminUserTeamGrantInfo!]! @since(version: "24.0.5")
grantedConnections: [AdminConnectionGrantInfo!]!
teamPermissions: [ID!]!
@@ -128,6 +134,7 @@ extend type Query {
listTeams(teamId: ID): [AdminTeamInfo!]!
listPermissions: [AdminPermissionInfo!]!
listAuthRoles: [String!]!
listTeamRoles: [String!]!
listTeamMetaParameters: [ObjectPropertyInfo!]!
createUser(userId: ID!, enabled: Boolean!, authRole: String): AdminUserInfo!
@@ -150,6 +157,8 @@ extend type Query {
setUserAuthRole(userId: ID!, authRole: String): Boolean
setUserTeamRole(userId: ID!, teamId: ID!, teamRole: String): Boolean @since(version: "24.0.5")
#### Connection management
# All connection configurations
@@ -21,6 +21,7 @@ import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.security.SMDataSourceGrant;
import org.jkiss.dbeaver.model.security.SMObjectType;
import org.jkiss.dbeaver.model.security.SMTeamMemberInfo;
import org.jkiss.dbeaver.model.security.user.SMTeam;
import java.util.ArrayList;
@@ -88,4 +89,8 @@ public class AdminTeamInfo {
return session.getAdminSecurityController().getTeamMembers(getTeamId());
}
@Property
public List<SMTeamMemberInfo> getGrantedUsersInfo() throws DBException {
return session.getAdminSecurityController().getTeamMembersInfo(getTeamId());
}
}
@@ -69,6 +69,9 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<String> listAuthRoles();
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
List<String> listTeamRoles();
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
boolean deleteUser(@NotNull WebSession webSession, String userName) throws DBWebException;
@@ -202,4 +205,10 @@ public interface DBWServiceAdmin extends DBWService {
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setUserAuthRole(WebSession webSession, String userId, String authRole) throws DBWebException;
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
Boolean setUserTeamRole(
@NotNull WebSession webSession, @NotNull String userId,
@NotNull String teamId, @Nullable String teamRole
) throws DBWebException;
}
@@ -60,6 +60,8 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
env -> getService(env).listPermissions(getWebSession(env)))
.dataFetcher("listAuthRoles",
env -> getService(env).listAuthRoles())
.dataFetcher("listTeamRoles",
env -> getService(env).listTeamRoles())
.dataFetcher("listTeamMetaParameters",
env -> getService(env).listTeamMetaParameters(getWebSession(env)))
.dataFetcher("createUser",
@@ -106,6 +108,14 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
env -> getService(env).enableUser(getWebSession(env), env.getArgument("userId"), env.getArgument("enabled")))
.dataFetcher("setUserAuthRole",
env -> getService(env).setUserAuthRole(getWebSession(env), env.getArgument("userId"), env.getArgument("authRole")))
.dataFetcher("setUserTeamRole",
env -> getService(env).setUserTeamRole(
getWebSession(env),
env.getArgument("userId"),
env.getArgument("teamId"),
env.getArgument("teamRole")
)
)
.dataFetcher("searchConnections", env -> getService(env).searchConnections(getWebSession(env), env.getArgument("hostNames")))
.dataFetcher("getConnectionSubjectAccess",
env -> getService(env).getConnectionSubjectAccess(
@@ -173,6 +173,11 @@ public class WebServiceAdmin implements DBWServiceAdmin {
return CBApplication.getInstance().getAvailableAuthRoles();
}
@Override
public List<String> listTeamRoles() {
return CBApplication.getInstance().getAvailableTeamRoles();
}
@Override
public boolean deleteUser(@NotNull WebSession webSession, String userName) throws DBWebException {
if (CommonUtils.equalObjects(userName, webSession.getUser().getUserId())) {
@@ -392,6 +397,21 @@ public class WebServiceAdmin implements DBWServiceAdmin {
}
}
@Override
public Boolean setUserTeamRole(
@NotNull WebSession webSession,
@NotNull String userId,
@NotNull String teamId,
@Nullable String teamRole
) throws DBWebException {
try {
webSession.getAdminSecurityController().setUserTeamRole(userId, teamId, teamRole);
return true;
} catch (Exception e) {
throw new DBWebException("Error updating user auth role", e);
}
}
////////////////////////////////////////////////////////////////////
// Connection management
@@ -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.96.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.97.qualifier
Bundle-Release-Date: 20240520
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.96-SNAPSHOT</version>
<version>1.0.97-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -134,7 +134,14 @@ type UserInfo {
metaParameters: Object!
# User configuration parameters
configurationParameters: Object!
# User teams
teams: [UserTeamInfo!]!
}
type UserTeamInfo {
teamId: String!
teamName: String!
teamRole: String
}
extend type Query {
@@ -19,6 +19,7 @@ package io.cloudbeaver.service.auth;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.model.user.WebUser;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.meta.Property;
@@ -88,4 +89,19 @@ public class WebUserInfo {
return session.getUserContext().getPreferenceStore().getCustomUserParameters();
}
@NotNull
@Property
public List<WebUserTeamInfo> getTeams() throws DBWebException {
if (session.getUserContext().isNonAnonymousUserAuthorizedInSM()) {
try {
return Arrays.stream(session.getSecurityController().getCurrentUserTeams())
.map(WebUserTeamInfo::new)
.toList();
} catch (DBException e) {
throw new DBWebException("Error reading user's teams", e);
}
} else {
return List.of();
}
}
}
@@ -0,0 +1,49 @@
/*
* 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.service.auth;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.security.user.SMUserTeam;
public class WebUserTeamInfo {
@NotNull
private final SMUserTeam userTeam;
public WebUserTeamInfo(@NotNull SMUserTeam userTeam) {
this.userTeam = userTeam;
}
@NotNull
@Property
public String getTeamId() {
return userTeam.getTeamId();
}
@NotNull
@Property
public String getTeamName() {
return userTeam.getTeamName();
}
@Nullable
@Property
public String getTeamRole() {
return userTeam.getTeamRole();
}
}
@@ -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.97.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.98.qualifier
Bundle-Release-Date: 20240520
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.97-SNAPSHOT</version>
<version>1.0.98-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -338,22 +338,17 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
DBRProgressMonitor monitor,
DataTransferProcessorDescriptor processor,
@NotNull DBSDataManipulator dataContainer,
Path path) throws DBException
{
Path path) throws DBException {
IDataTransferProcessor processorInstance = processor.getInstance();
StreamTransferProducer producer;
if (dataContainer.getDataSource() != null) {
DatabaseTransferConsumer consumer = new DatabaseTransferConsumer(dataContainer);
producer = new StreamTransferProducer(new StreamEntityMapping(path), processor);
DatabaseTransferConsumer consumer = new DatabaseTransferConsumer(dataContainer);
DatabaseConsumerSettings databaseConsumerSettings = new DatabaseConsumerSettings();
databaseConsumerSettings.setContainer((DBSObjectContainer) dataContainer.getDataSource());
consumer.setSettings(databaseConsumerSettings);
DatabaseMappingContainer databaseMappingContainer = new DatabaseMappingContainer(databaseConsumerSettings, dataContainer);
databaseMappingContainer.getAttributeMappings(monitor);
databaseMappingContainer.setTarget(dataContainer);
consumer.setContainerMapping(databaseMappingContainer);
StreamTransferProducer producer = new StreamTransferProducer(new StreamEntityMapping(path), processor);
StreamProducerSettings producerSettings = new StreamProducerSettings();
Map<String, Object> properties = new HashMap<>();
@@ -366,7 +361,11 @@ public class WebServiceDataTransfer implements DBWServiceDataTransfer {
producer,
processorInstance,
properties);
DatabaseMappingContainer databaseMappingContainer =
new DatabaseMappingContainer(databaseConsumerSettings, producer.getDatabaseObject());
databaseMappingContainer.getAttributeMappings(monitor);
databaseMappingContainer.setTarget(dataContainer);
consumer.setContainerMapping(databaseMappingContainer);
try {
producer.transferData(monitor, consumer, processorInstance, producerSettings, null);
} catch (DBException e) {
@@ -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.14.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.15.qualifier
Bundle-Release-Date: 20240520
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.14-SNAPSHOT</version>
<version>1.0.15-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.100.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.101.qualifier
Bundle-Release-Date: 20240520
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.100-SNAPSHOT</version>
<version>1.0.101-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.14.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.15.qualifier
Bundle-Release-Date: 20240520
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.14-SNAPSHOT</version>
<version>1.0.15-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.49.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.50.qualifier
Bundle-Release-Date: 20240520
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.49-SNAPSHOT</version>
<version>1.0.50-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -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.52.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.53.qualifier
Bundle-Release-Date: 20240520
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
@@ -130,6 +130,7 @@ CREATE TABLE {table_prefix}CB_USER_TEAM
(
USER_ID VARCHAR(128) NOT NULL,
TEAM_ID VARCHAR(128) NOT NULL,
TEAM_ROLE VARCHAR(128),
GRANT_TIME TIMESTAMP NOT NULL,
GRANTED_BY VARCHAR(128) NOT NULL,
@@ -0,0 +1,2 @@
ALTER TABLE {table_prefix}CB_USER_TEAM
ADD TEAM_ROLE VARCHAR(128) NULL;
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.service.security</artifactId>
<version>1.0.52-SNAPSHOT</version>
<version>1.0.53-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
@@ -19,6 +19,7 @@ package io.cloudbeaver.service.security;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.auth.*;
import io.cloudbeaver.model.app.WebAppConfiguration;
import io.cloudbeaver.model.app.WebAuthApplication;
@@ -49,7 +50,6 @@ import org.jkiss.dbeaver.model.security.exception.SMException;
import org.jkiss.dbeaver.model.security.exception.SMRefreshTokenExpiredException;
import org.jkiss.dbeaver.model.security.user.*;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.websocket.event.WSEventType;
import org.jkiss.dbeaver.model.websocket.event.WSUserCloseSessionsEvent;
import org.jkiss.dbeaver.model.websocket.event.WSUserDeletedEvent;
import org.jkiss.dbeaver.model.websocket.event.permissions.WSObjectPermissionEvent;
@@ -163,7 +163,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
dbStat.execute();
}
saveSubjectMetas(dbCon, userId, metaParameters);
String defaultTeamName = application.getAppConfiguration().getDefaultUserTeam();
String defaultTeamName = getDefaultUserTeam();
if (!CommonUtils.isEmpty(defaultTeamName)) {
setUserTeams(dbCon, userId, new String[]{defaultTeamName}, userId);
}
@@ -208,7 +208,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
} catch (SQLException e) {
throw new DBCException("Error deleting user from database", e);
}
var event = new WSUserDeletedEvent(WSEventType.USER_DELETED, userId);
var event = new WSUserDeletedEvent(userId);
application.getEventController().addEvent(event);
}
@@ -224,23 +224,72 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
addSubjectPermissionsUpdateEvent(userId, SMSubjectType.user);
}
public void setUserTeams(@NotNull Connection dbCon, String userId, String[] teamIds, String grantorId)
@Override
public void setUserTeamRole(
@NotNull String userId,
@NotNull String teamId,
@Nullable String teamRole
) throws DBException {
if (!isSubjectExists(userId)) {
throw new DBCException("User '" + userId + "' doesn't exists");
}
if (!isSubjectExists(teamId)) {
throw new DBCException("Team '" + teamId + "' doesn't exists");
}
try (
var dbCon = database.openConnection();
PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("UPDATE {table_prefix}CB_USER_TEAM " +
"SET TEAM_ROLE=? WHERE USER_ID=? AND TEAM_ID=?"))
) {
JDBCUtils.setStringOrNull(dbStat, 1, teamRole);
dbStat.setString(2, userId);
dbStat.setString(3, teamId);
dbStat.execute();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//TODO implement add/delete user teams api
protected void setUserTeams(@NotNull Connection dbCon, String userId, String[] teamIds, String grantorId)
throws SQLException {
JDBCUtils.executeStatement(
dbCon,
database.normalizeTableNames("DELETE FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"),
userId
);
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
String deleteUserTeamsSql = "DELETE FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?";
if (!ArrayUtils.isEmpty(teamIds)) {
deleteUserTeamsSql =
deleteUserTeamsSql + " AND TEAM_ID NOT IN (" + SQLUtils.generateParamList(teamIds.length) + ")";
}
try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(deleteUserTeamsSql))) {
int index = 1;
dbStat.setString(index++, userId);
for (String teamId : teamIds) {
dbStat.setString(index++, teamId);
}
dbStat.execute();
}
String defaultUserTeam = getDefaultUserTeam();
if (CommonUtils.isNotEmpty(defaultUserTeam) && !ArrayUtils.contains(teamIds, defaultUserTeam)) {
teamIds = ArrayUtils.add(String.class, teamIds, defaultUserTeam);
}
if (!ArrayUtils.isEmpty(teamIds)) {
Set<String> currentUserTeams = new HashSet<>(JDBCUtils.queryStrings(
dbCon,
database.normalizeTableNames("SELECT TEAM_ID FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"),
userId
));
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("INSERT INTO {table_prefix}CB_USER_TEAM" +
"(USER_ID,TEAM_ID,GRANT_TIME,GRANTED_BY) VALUES(?,?,?,?)"))
) {
for (String teamId : teamIds) {
if (currentUserTeams.contains(teamId)) {
continue;
}
dbStat.setString(1, userId);
dbStat.setString(2, teamId);
dbStat.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
@@ -253,12 +302,12 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@NotNull
@Override
public SMTeam[] getUserTeams(String userId) throws DBException {
Map<String, SMTeam> teams = new LinkedHashMap<>();
public SMUserTeam[] getUserTeams(String userId) throws DBException {
Map<String, SMUserTeam> teams = new LinkedHashMap<>();
try (Connection dbCon = database.openConnection()) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
String defaultUserTeam = getDefaultUserTeam();
try (PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(
"SELECT R.*,S.IS_SECRET_STORAGE FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R, " +
"SELECT R.*,S.IS_SECRET_STORAGE,UR.TEAM_ROLE FROM {table_prefix}CB_USER_TEAM UR, {table_prefix}CB_TEAM R, " +
"{table_prefix}CB_AUTH_SUBJECT S " +
"WHERE UR.USER_ID=? AND UR.TEAM_ID = R.TEAM_ID " +
"AND S.SUBJECT_ID IN (R.TEAM_ID,?)"))
@@ -268,12 +317,13 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
var team = fetchTeam(dbResult);
teams.put(team.getTeamId(), team);
String teamRole = dbResult.getString("TEAM_ROLE");
teams.put(team.getTeamId(), new SMUserTeam(team, teamRole));
}
}
}
readSubjectsMetas(dbCon, SMSubjectType.team, null, teams);
return teams.values().toArray(new SMTeam[0]);
return teams.values().toArray(new SMUserTeam[0]);
} catch (SQLException e) {
throw new DBCException("Error while reading user teams", e);
}
@@ -297,7 +347,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@NotNull
@Override
public SMTeam[] getCurrentUserTeams() throws DBException {
public SMUserTeam[] getCurrentUserTeams() throws DBException {
return getUserTeams(getUserIdOrThrow());
}
@@ -325,7 +375,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT TEAM_ID FROM {table_prefix}CB_USER_TEAM WHERE USER_ID=?"))
) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
String defaultUserTeam = getDefaultUserTeam();
dbStat.setString(1, userId);
try (ResultSet dbResult = dbStat.executeQuery()) {
Set<String> teamIDs = new LinkedHashSet<>();
@@ -936,7 +986,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@Override
public SMTeam[] readAllTeams() throws DBCException {
try (Connection dbCon = database.openConnection()) {
String defaultUserTeam = application.getAppConfiguration().getDefaultUserTeam();
String defaultUserTeam = getDefaultUserTeam();
Map<String, SMTeam> teams = new LinkedHashMap<>();
String query = database.normalizeTableNames(
"SELECT T.*, S.IS_SECRET_STORAGE FROM {table_prefix}CB_TEAM T, " +
@@ -981,32 +1031,43 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@NotNull
@Override
public String[] getTeamMembers(String teamId) throws DBCException {
public String[] getTeamMembers(String teamId) throws DBException {
return getTeamMembersInfo(teamId).stream().map(SMTeamMemberInfo::userId).toArray(String[]::new);
}
@NotNull
@Override
public List<SMTeamMemberInfo> getTeamMembersInfo(@NotNull String teamId) throws DBException {
try (Connection dbCon = database.openConnection()) {
if (application.getAppConfiguration().getDefaultUserTeam().equals(teamId)) {
Map<String, String> usersRoles = new LinkedHashMap<>();
if (getDefaultUserTeam().equals(teamId)) {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER"))) {
List<String> subjects = new ArrayList<>();
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER"))
) {
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
subjects.add(dbResult.getString(1));
usersRoles.put(dbResult.getString(1), null);
}
}
return subjects.toArray(new String[0]);
}
} else {
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames("SELECT USER_ID FROM {table_prefix}CB_USER_TEAM WHERE TEAM_ID=?"))) {
dbStat.setString(1, teamId);
List<String> subjects = new ArrayList<>();
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
subjects.add(dbResult.getString(1));
}
}
return subjects.toArray(new String[0]);
}
}
try (PreparedStatement dbStat = dbCon.prepareStatement(
database.normalizeTableNames(
"SELECT USER_ID,TEAM_ROLE FROM {table_prefix}CB_USER_TEAM WHERE TEAM_ID=?"))
) {
dbStat.setString(1, teamId);
try (ResultSet dbResult = dbStat.executeQuery()) {
while (dbResult.next()) {
String userId = dbResult.getString(1);
String teamRole = dbResult.getString(2);
usersRoles.put(userId, teamRole);
}
}
}
return usersRoles.entrySet()
.stream()
.map(entry -> new SMTeamMemberInfo(entry.getKey(), entry.getValue()))
.toList();
} catch (SQLException e) {
throw new DBCException("Error while reading team members", e);
}
@@ -1095,7 +1156,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@Override
public void deleteTeam(String teamId, boolean force) throws DBCException {
String defaultUsersTeam = application.getAppConfiguration().getDefaultUserTeam();
String defaultUsersTeam = getDefaultUserTeam();
if (CommonUtils.isNotEmpty(defaultUsersTeam) && defaultUsersTeam.equals(teamId)) {
throw new DBCException("Default users team cannot be deleted");
}
@@ -1844,6 +1905,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
} catch (SQLException e) {
throw new DBCException("Session invalidation failed", e);
}
application.getEventController().addEvent(new WSUserCloseSessionsEvent(List.of()));
}
private void invalidateAllUserTokens(@NotNull String userId) throws DBCException {
@@ -2337,7 +2399,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
List<String> smSessionsId = findActiveUserSessions(userId, currentTime)
.stream().map(SMActiveSession::sessionId).collect(Collectors.toList());
deleteSessionsTokens(smSessionsId);
application.getEventController().addEvent(new WSUserCloseSessionsEvent(smSessionsId, WSEventType.CLOSE_USER_SESSIONS));
application.getEventController().addEvent(new WSUserCloseSessionsEvent(smSessionsId));
}
/**
@@ -2727,6 +2789,42 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
}
}
@Override
public boolean hasAccessToUsers(@NotNull String teamRole, @NotNull Set<String> userIds) throws DBException {
if (CommonUtils.isEmpty(userIds)) {
return true;
}
String currentUserId = getUserIdOrThrow();
var currentPermissions = getUserPermissions(currentUserId);
if (currentPermissions.contains(DBWConstants.PERMISSION_ADMIN)) {
return true;
}
String sql = "SELECT COUNT(DISTINCT UT.USER_ID) FROM {table_prefix}CB_USER_TEAM UT " +
"WHERE TEAM_ID IN (SELECT TEAM_ID FROM {table_prefix}CB_USER_TEAM WHERE USER_ID = ? and TEAM_ROLE = ?) " +
"AND UT.USER_ID IN(" + SQLUtils.generateParamList(userIds.size()) + ")";
try (var dbCon = database.openConnection();
var dbStat = dbCon.prepareStatement(database.normalizeTableNames(sql))
) {
dbStat.setString(1, currentUserId);
dbStat.setString(2, teamRole);
int index = 3;
for (String userId : userIds) {
dbStat.setString(index++, userId);
}
try (ResultSet dbResult = dbStat.executeQuery()) {
if (dbResult.next()) {
int matchesUsersCount = dbResult.getInt(1);
return matchesUsersCount == userIds.size();
} else {
return false;
}
}
} catch (SQLException e) {
throw new DBCException("Error validating user access", e);
}
}
@Override
public void deleteAllSubjectObjectPermissions(@NotNull String subjectId, @NotNull SMObjectType objectType) throws DBException {
try (Connection dbCon = database.openConnection()) {
@@ -2977,7 +3075,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
@NotNull
private String getUserIdOrThrow() throws SMException {
protected String getUserIdOrThrow() throws SMException {
String userId = getUserIdOrNull();
if (userId == null) {
throw new SMException("User not authenticated");
@@ -2986,7 +3084,7 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
}
@Nullable
private String getUserIdOrNull() {
protected String getUserIdOrNull() {
SMCredentials activeUserCredentials = credentialsProvider.getActiveUserCredentials();
if (activeUserCredentials == null || activeUserCredentials.getUserId() == null) {
return null;
@@ -3081,4 +3179,9 @@ public class CBEmbeddedSecurityController<T extends WebAuthApplication>
var credentials = credentialsProvider.getActiveUserCredentials();
return credentials == null ? null : credentials.getUserId();
}
@NotNull
private String getDefaultUserTeam() {
return application.getAppConfiguration().getDefaultUserTeam();
}
}
@@ -74,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 = 19;
private static final int CURRENT_SCHEMA_VERSION = 20;
private static final String DEFAULT_DB_USER_NAME = "cb-data";
private static final String DEFAULT_DB_PWD_FILE = ".database-credentials.dat";
@@ -550,8 +550,6 @@ public class CBDatabase {
}
private String getCurrentInstanceId() throws IOException {
// 12 chars - mac address
String macAddress = CommonUtils.toHexString(RuntimeUtils.getLocalMacAddress());
// 16 chars - workspace ID
String workspaceId = DBWorkbench.getPlatform().getWorkspace().getWorkspaceId();
if (workspaceId.length() > 16) {
@@ -559,7 +557,7 @@ public class CBDatabase {
}
StringBuilder id = new StringBuilder(36);
id.append(macAddress);
id.append("000000000000"); // there was mac address, but it generates dynamically when docker is used
id.append(":").append(workspaceId).append(":");
while (id.length() < 36) {
id.append("X");
@@ -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.12.qualifier
Bundle-Release-Date: 20240506
Bundle-Version: 1.0.13.qualifier
Bundle-Release-Date: 20240520
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
+1 -1
View File
@@ -10,7 +10,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.slf4j</artifactId>
<version>1.0.12-SNAPSHOT</version>
<version>1.0.13-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
+1 -1
View File
@@ -18,7 +18,7 @@
<dependency>
<groupId>org.duckdb</groupId>
<artifactId>duckdb_jdbc</artifactId>
<version>0.9.2</version>
<version>0.10.2</version>
</dependency>
</dependencies>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ce.drivers.feature"
label="Cloudbeaver CE 3rd Party Drivers"
version="1.0.120.qualifier"
version="1.0.121.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.120-SNAPSHOT</version>
<version>1.0.121-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.product.ce.feature"
label="%featureName"
version="24.0.4.qualifier"
version="24.0.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>24.0.4-SNAPSHOT</version>
<version>24.0.5-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
<build>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.server.feature"
label="%featureName"
version="24.0.4.qualifier"
version="24.0.5.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.server">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.server.feature</artifactId>
<version>24.0.4-SNAPSHOT</version>
<version>24.0.5-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
@@ -2,7 +2,7 @@
<feature
id="io.cloudbeaver.ws.feature"
label="%featureName"
version="1.0.50.qualifier"
version="1.0.51.qualifier"
provider-name="%providerName"
plugin="io.cloudbeaver.model">
@@ -10,6 +10,6 @@
<relativePath>../</relativePath>
</parent>
<artifactId>io.cloudbeaver.ws.feature</artifactId>
<version>1.0.50-SNAPSHOT</version>
<version>1.0.51-SNAPSHOT</version>
<packaging>eclipse-feature</packaging>
</project>
+1 -1
View File
@@ -19,7 +19,7 @@
<properties>
<dbeaver-product>CloudBeaver CE</dbeaver-product>
<dbeaver-version>24.0.4</dbeaver-version>
<dbeaver-version>24.0.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="24.0.4.qualifier" useFeatures="true"
application="io.cloudbeaver.product.ce.application" version="24.0.5.qualifier" useFeatures="true"
includeLaunchers="false">
<configIni use="default">
+1 -1
View File
@@ -9,7 +9,7 @@
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../</relativePath>
</parent>
<version>24.0.4-SNAPSHOT</version>
<version>24.0.5-SNAPSHOT</version>
<artifactId>web-server</artifactId>
<packaging>eclipse-repository</packaging>
<name>Cloudbeaver Server Product</name>
@@ -25,5 +25,9 @@ Require-Bundle: org.eclipse.core.runtime,
io.cloudbeaver.product.ce,
io.cloudbeaver.service.auth,
io.cloudbeaver.service.rm,
io.cloudbeaver.service.rm.nio
io.cloudbeaver.service.rm.nio,
org.jkiss.dbeaver.ext.mysql,
org.jkiss.dbeaver.ext.postgresql,
org.jkiss.dbeaver.ext.oracle,
org.jkiss.dbeaver.ext.mssql
Export-Package: io.cloudbeaver.test.platform
@@ -35,6 +35,7 @@ import java.nio.file.Path;
@Suite.SuiteClasses(
{
ConnectionsTest.class,
SQLQueryTranslatorTest.class,
AuthenticationTest.class,
ResourceManagerTest.class,
RMLockTest.class,
@@ -0,0 +1,200 @@
/*
* 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.test.platform;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.ext.h2.model.H2SQLDialect;
import org.jkiss.dbeaver.ext.mssql.model.SQLServerDialect;
import org.jkiss.dbeaver.ext.mysql.model.MySQLDialect;
import org.jkiss.dbeaver.ext.oracle.model.OracleSQLDialect;
import org.jkiss.dbeaver.ext.postgresql.model.PostgreDialect;
import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect;
import org.jkiss.dbeaver.model.sql.SQLDialect;
import org.jkiss.dbeaver.model.sql.translate.SQLQueryTranslator;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class SQLQueryTranslatorTest {
@Test
public void createSimpleTable() throws DBException {
var basicSql = "CREATE TABLE CB_AUTH_SUBJECT (SUBJECT_ID VARCHAR(128) NOT NULL," +
" SUBJECT_TYPE VARCHAR(8) NOT NULL," +
" IS_SECRET_STORAGE CHAR(1) DEFAULT 'Y' NOT NULL," +
" PRIMARY KEY (SUBJECT_ID));\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(
new PostgreDialect(),
"CREATE TABLE CB_AUTH_SUBJECT (SUBJECT_ID VARCHAR (128) NOT NULL,\n" +
"SUBJECT_TYPE VARCHAR (8) NOT NULL,\n" +
"IS_SECRET_STORAGE CHAR (1) DEFAULT 'Y' NOT NULL,\n" +
"PRIMARY KEY (SUBJECT_ID));\n"
);
expectedSqlByDialect.put(new MySQLDialect(),
"CREATE TABLE CB_AUTH_SUBJECT (SUBJECT_ID VARCHAR (128) NOT NULL,\n" +
"SUBJECT_TYPE VARCHAR (8) NOT NULL,\n" +
"IS_SECRET_STORAGE CHAR (1) DEFAULT 'Y' NOT NULL,\n" +
"PRIMARY KEY (SUBJECT_ID));\n");
expectedSqlByDialect.put(new OracleSQLDialect(), basicSql);
expectedSqlByDialect.put(new SQLServerDialect(), basicSql);
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void addColumn() throws DBException {
var basicSql = "ALTER TABLE CB_AUTH_ATTEMPT ADD ERROR_CODE VARCHAR(128) NULL;\n";
//same for all dialects
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), basicSql);
expectedSqlByDialect.put(new MySQLDialect(), basicSql);
expectedSqlByDialect.put(new OracleSQLDialect(), basicSql);
expectedSqlByDialect.put(new SQLServerDialect(), basicSql);
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void alterColumn() throws DBException {
var basicSql = "ALTER TABLE CB_TABLE ALTER COLUMN CB_COLUMN SET NULL;\n";
// we use custom scripts for postgres/mysql/oracle
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new OracleSQLDialect(), "ALTER TABLE CB_TABLE MODIFY CB_COLUMN NULL;\n");
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void createTableWithUuid() throws DBException {
var basicSql = "CREATE TABLE CB_TEST_TYPES (UUID_COLUMN UUID);\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), basicSql);
expectedSqlByDialect.put(
new MySQLDialect(),
"CREATE TABLE CB_TEST_TYPES (UUID_COLUMN CHAR(36));\n"
);
expectedSqlByDialect.put(new OracleSQLDialect(), "CREATE TABLE CB_TEST_TYPES (UUID_COLUMN VARCHAR2(36));\n");
expectedSqlByDialect.put(
new SQLServerDialect(),
"CREATE TABLE CB_TEST_TYPES (UUID_COLUMN UNIQUEIDENTIFIER);\n"
);
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void createTableWithBoolean() throws DBException {
var basicSql = "CREATE TABLE CB_TEST_TYPES (BOOLEAN_COLUMN BOOLEAN);\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), basicSql);
expectedSqlByDialect.put(new MySQLDialect(), "CREATE TABLE CB_TEST_TYPES (BOOLEAN_COLUMN TINYINT(1));\n");
expectedSqlByDialect.put(
new OracleSQLDialect(),
"CREATE TABLE CB_TEST_TYPES (BOOLEAN_COLUMN VARCHAR(1));\n"
);
expectedSqlByDialect.put(
new SQLServerDialect(),
"CREATE TABLE CB_TEST_TYPES (BOOLEAN_COLUMN BIT);\n"
);
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void createTableWithBlob() throws DBException {
var basicSql = "CREATE TABLE CB_TEST_TYPES (BLOB_COLUMN BLOB);\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), "CREATE TABLE CB_TEST_TYPES (BLOB_COLUMN BYTEA);\n");
expectedSqlByDialect.put(new MySQLDialect(), basicSql);
expectedSqlByDialect.put(new OracleSQLDialect(), basicSql);
expectedSqlByDialect.put(new SQLServerDialect(), "CREATE TABLE CB_TEST_TYPES (BLOB_COLUMN IMAGE);\n");
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void createTableWithBigint() throws DBException {
var basicSql = "CREATE TABLE CB_TEST_TYPES (BIGINT_COLUMN BIGINT);\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), basicSql);
expectedSqlByDialect.put(new MySQLDialect(), basicSql);
expectedSqlByDialect.put(new OracleSQLDialect(), "CREATE TABLE CB_TEST_TYPES (BIGINT_COLUMN NUMBER);\n");
expectedSqlByDialect.put(new SQLServerDialect(), basicSql);
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
@Test
public void createTableWithAutoincrement() throws DBException {
var basicSql = "CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT AUTO_INCREMENT NOT NULL);\n";
Map<SQLDialect, String> expectedSqlByDialect = new HashMap<>();
expectedSqlByDialect.put(new H2SQLDialect(), basicSql);
expectedSqlByDialect.put(new PostgreDialect(), "CREATE SEQUENCE CB_TEST_TYPES_AUTOINC_COLUMN;\n" +
"CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT NOT NULL DEFAULT NEXTVAL('CB_TEST_TYPES_AUTOINC_COLUMN'));" +
"\n");
expectedSqlByDialect.put(new MySQLDialect(), basicSql);
expectedSqlByDialect.put(
new OracleSQLDialect(),
"CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL);\n");
expectedSqlByDialect.put(
new SQLServerDialect(),
"CREATE TABLE CB_TEST_TYPES (AUTOINC_COLUMN BIGINT IDENTITY NOT NULL);\n");
translateAndValidateQueries(basicSql, expectedSqlByDialect);
}
private static void translateAndValidateQueries(
@NotNull String basicSql,
@NotNull Map<SQLDialect, String> expectedSqlByDialect
) throws DBException {
var preferenceStore = DBWorkbench.getPlatform().getPreferenceStore();
SQLDialect sourceDialect = new BasicSQLDialect() {
};
for (Map.Entry<SQLDialect, String> entry : expectedSqlByDialect.entrySet()) {
String translated = SQLQueryTranslator.translateScript(
sourceDialect,
entry.getKey(),
preferenceStore,
basicSql
);
Assert.assertEquals(
entry.getKey().getDialectId() + " has invalid syntax " + translated,
entry.getValue().toLowerCase(),
translated.toLowerCase()
);
}
}
}
+2 -2
View File
@@ -44,11 +44,11 @@
"react-dom": "^18.2.0",
"reakit": "~1.3.11",
"reflect-metadata": "^0.2.1",
"reshadow": "^0.0.1",
"rimraf": "~5.0.5",
"typescript": "^5.3.3",
"typescript-plugin-css-modules": "^5.1.0"
},
"dependencies": {},
"prettier": "@cloudbeaver/prettier-config"
"prettier": "@cloudbeaver/prettier-config",
"packageManager": "yarn@1.22.21"
}
@@ -7,16 +7,16 @@
*/
import type { PluginManifest } from '@cloudbeaver/core-di';
import { AdministrationItemService } from './AdministrationItem/AdministrationItemService';
import { AdministrationLocaleService } from './AdministrationLocaleService';
import { AdministrationScreenService } from './AdministrationScreen/AdministrationScreenService';
import { ConfigurationWizardService } from './AdministrationScreen/ConfigurationWizard/ConfigurationWizardService';
import { PermissionsResource } from './PermissionsResource';
export const coreAdministrationManifest: PluginManifest = {
info: {
name: 'Core Administration',
},
providers: [AdministrationItemService, PermissionsResource, AdministrationScreenService, ConfigurationWizardService, AdministrationLocaleService],
providers: [
() => import('./AdministrationItem/AdministrationItemService').then(m => m.AdministrationItemService),
() => import('./PermissionsResource').then(m => m.PermissionsResource),
() => import('./AdministrationScreen/AdministrationScreenService').then(m => m.AdministrationScreenService),
() => import('./AdministrationScreen/ConfigurationWizard/ConfigurationWizardService').then(m => m.ConfigurationWizardService),
() => import('./AdministrationLocaleService').then(m => m.AdministrationLocaleService),
],
};
+9 -2
View File
@@ -1,6 +1,13 @@
/*
* 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.
*/
.bodyContent {
composes: theme-background-surface theme-text-on-surface theme-typography from global;
height: var(--app-height);
height: var(--app-height, 100vh);
display: flex;
padding: 0 !important;
flex-direction: column;
@@ -8,5 +15,5 @@
}
.loader {
height: var(--app-height);
height: var(--app-height, 100vh);
}
+6 -2
View File
@@ -54,8 +54,12 @@ export const Body = observer(function Body() {
<Loader className={s(styles, { loader: true })} suspense>
{Screen && <Screen {...screenService.routerService.params} />}
</Loader>
<DialogsPortal />
<Notifications />
<Loader className={s(styles, { loader: true })} suspense>
<DialogsPortal />
</Loader>
<Loader className={s(styles, { loader: true })} suspense>
<Notifications />
</Loader>
</div>
</Loader>
</DNDProvider>
+5 -5
View File
@@ -7,14 +7,14 @@
*/
import type { PluginManifest } from '@cloudbeaver/core-di';
import { AppLocaleService } from './AppLocaleService';
import { AppScreenBootstrap } from './AppScreen/AppScreenBootstrap';
import { AppScreenService } from './AppScreen/AppScreenService';
export const coreAppManifest: PluginManifest = {
info: {
name: 'Core App',
},
providers: [AppScreenService, AppScreenBootstrap, AppLocaleService],
providers: [
() => import('./AppScreen/AppScreenService').then(m => m.AppScreenService),
() => import('./AppScreen/AppScreenBootstrap').then(m => m.AppScreenBootstrap),
() => import('./AppLocaleService').then(m => m.AppLocaleService),
],
};
@@ -0,0 +1,40 @@
/*
* 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 { injectable } from '@cloudbeaver/core-di';
import { CachedDataResource } from '@cloudbeaver/core-resource';
import { EAdminPermission, SessionPermissionsResource } from '@cloudbeaver/core-root';
import { GraphQLService } from '@cloudbeaver/core-sdk';
export const USER_TEAM_ROLE_SUPERVISOR = 'Supervisor';
@injectable()
export class TeamRolesResource extends CachedDataResource<string[]> {
constructor(
private readonly graphQLService: GraphQLService,
sessionPermissionsResource: SessionPermissionsResource,
) {
super(() => []);
sessionPermissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
}
async assignTeamRoleToUser(userId: string, teamId: string, teamRole: string | null) {
const { result } = await this.graphQLService.sdk.updateUserTeamRole({
userId,
teamId,
teamRole: teamRole ?? undefined,
});
return result;
}
protected async loader(): Promise<string[]> {
const { roles } = await this.graphQLService.sdk.getTeamRoles();
return roles;
}
}
@@ -15,12 +15,19 @@ import {
type ResourceKeySimple,
ResourceKeyUtils,
} from '@cloudbeaver/core-resource';
import { AdminConnectionGrantInfo, AdminTeamInfoFragment, GetTeamsListQueryVariables, GraphQLService } from '@cloudbeaver/core-sdk';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import {
AdminConnectionGrantInfo,
AdminTeamInfoFragment,
AdminUserTeamGrantInfo,
GetTeamsListQueryVariables,
GraphQLService,
} from '@cloudbeaver/core-sdk';
import { isArraysEqual, UndefinedToNull } from '@cloudbeaver/core-utils';
const NEW_TEAM_SYMBOL = Symbol('new-team');
export type TeamInfo = AdminTeamInfoFragment;
export type UserTeamGrantInfo = UndefinedToNull<AdminUserTeamGrantInfo>;
type TeamResourceIncludes = Omit<GetTeamsListQueryVariables, 'teamId'>;
type NewTeam = TeamInfo & { [NEW_TEAM_SYMBOL]: boolean; timestamp: number };
@@ -85,9 +92,9 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResou
return this.data;
}
async loadGrantedUsers(teamId: string): Promise<string[]> {
async loadGrantedUsers(teamId: string): Promise<UserTeamGrantInfo[]> {
const { team } = await this.graphQLService.sdk.getTeamGrantedUsers({ teamId });
return team[0].grantedUsers;
return team[0].grantedUsersInfo.map(user => ({ userId: user.userId, teamRole: user.teamRole ?? null }));
}
async getSubjectConnectionAccess(subjectId: string): Promise<AdminConnectionGrantInfo[]> {
@@ -40,6 +40,10 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void,
return this.data?.authRole as ELMRole | undefined;
}
get teams() {
return this.data?.teams || [];
}
get parametersAvailable() {
return this.data !== null;
}
@@ -1,3 +1,10 @@
/*
* 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.
*/
export * from './manifest';
export * from './ELMRole';
export * from './AppAuthService';
@@ -20,3 +27,4 @@ export * from './UsersResource';
export * from './TeamMetaParametersResource';
export * from './AUTH_SETTINGS_GROUP';
export * from './PasswordPolicyService';
export * from './TeamRolesResource';
@@ -7,48 +7,30 @@
*/
import type { PluginManifest } from '@cloudbeaver/core-di';
import { AppAuthService } from './AppAuthService';
import { AuthConfigurationParametersResource } from './AuthConfigurationParametersResource';
import { AuthConfigurationsResource } from './AuthConfigurationsResource';
import { AuthInfoService } from './AuthInfoService';
import { AuthProviderService } from './AuthProviderService';
import { AuthProvidersResource } from './AuthProvidersResource';
import { AuthRolesResource } from './AuthRolesResource';
import { AuthSettingsService } from './AuthSettingsService';
import { LocaleService } from './LocaleService';
import { PasswordPolicyService } from './PasswordPolicyService';
import { TeamMetaParametersResource } from './TeamMetaParametersResource';
import { TeamsManagerService } from './TeamsManagerService';
import { TeamsResource } from './TeamsResource';
import { UserConfigurationBootstrap } from './UserConfigurationBootstrap';
import { UserDataService } from './UserDataService';
import { UserInfoResource } from './UserInfoResource';
import { UserMetaParametersResource } from './UserMetaParametersResource';
import { UsersResource } from './UsersResource';
export const coreAuthenticationManifest: PluginManifest = {
info: {
name: 'Core Authentication',
},
providers: [
AppAuthService,
AuthInfoService,
AuthProviderService,
AuthProvidersResource,
AuthSettingsService,
AuthConfigurationsResource,
AuthConfigurationParametersResource,
TeamsManagerService,
TeamsResource,
UserDataService,
UserInfoResource,
UsersResource,
UserMetaParametersResource,
UserConfigurationBootstrap,
AuthRolesResource,
TeamMetaParametersResource,
PasswordPolicyService,
LocaleService,
() => import('./AppAuthService').then(m => m.AppAuthService),
() => import('./AuthConfigurationParametersResource').then(m => m.AuthConfigurationParametersResource),
() => import('./AuthConfigurationsResource').then(m => m.AuthConfigurationsResource),
() => import('./AuthInfoService').then(m => m.AuthInfoService),
() => import('./AuthProviderService').then(m => m.AuthProviderService),
() => import('./AuthProvidersResource').then(m => m.AuthProvidersResource),
() => import('./AuthRolesResource').then(m => m.AuthRolesResource),
() => import('./AuthSettingsService').then(m => m.AuthSettingsService),
() => import('./LocaleService').then(m => m.LocaleService),
() => import('./PasswordPolicyService').then(m => m.PasswordPolicyService),
() => import('./TeamMetaParametersResource').then(m => m.TeamMetaParametersResource),
() => import('./TeamsManagerService').then(m => m.TeamsManagerService),
() => import('./TeamsResource').then(m => m.TeamsResource),
() => import('./TeamRolesResource').then(m => m.TeamRolesResource),
() => import('./UserConfigurationBootstrap').then(m => m.UserConfigurationBootstrap),
() => import('./UserDataService').then(m => m.UserDataService),
() => import('./UserInfoResource').then(m => m.UserInfoResource),
() => import('./UserMetaParametersResource').then(m => m.UserMetaParametersResource),
() => import('./UsersResource').then(m => m.UsersResource),
],
};
+1 -2
View File
@@ -34,8 +34,7 @@
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reakit": "~1.3.11",
"reakit-utils": "^0.15.2",
"reshadow": "^0.0.1"
"reakit-utils": "^0.15.2"
},
"peerDependencies": {},
"devDependencies": {
+31 -18
View File
@@ -1,30 +1,43 @@
/*
* 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.
*/
.main {
position: relative;
display: flex;
align-items: center;
padding: 8px;
& .before {
width: 24px;
height: 24px;
flex-shrink: 0;
}
& .after {
flex-shrink: 0;
}
& .info {
line-height: 1.4;
font-weight: 500;
}
& .description {
line-height: 1.2;
}
}
.before {
margin-right: 16px;
width: 24px;
height: 24px;
flex-shrink: 0;
}
.after {
margin-left: 16px;
flex-shrink: 0;
.big {
& .before {
width: 32px;
height: 32px;
}
}
.info {
composes: theme-typography--body2 from global;
flex: 1;
line-height: 1.4;
display: flex;
flex-direction: column;
font-weight: 500;
}
.description {
composes: theme-typography--caption from global;
line-height: 1.2;
}
.ripple {
+19 -9
View File
@@ -8,6 +8,7 @@
import { observer } from 'mobx-react-lite';
import style from './Cell.m.css';
import { Container } from './Containers/Container';
import { s } from './s';
import { useS } from './useS';
@@ -16,23 +17,32 @@ interface Props {
before?: React.ReactElement;
after?: React.ReactElement;
ripple?: boolean;
big?: boolean;
className?: string;
children?: React.ReactNode;
}
export const Cell = observer<Props>(function Cell({ before, after, description, className, ripple = true, children }) {
export const Cell = observer<Props>(function Cell({ before, after, description, className, ripple = true, big, children }) {
const styles = useS(style);
return (
<div className={s(styles, { ripple }, className)}>
<main className={s(styles, { main: true })}>
<div className={s(styles, { before: true })}>{before}</div>
<div className={s(styles, { info: true })}>
<div className={s(styles, { ripple, big }, className)}>
<Container className={s(styles, { main: true })} gap parent center dense>
{before && (
<Container className={s(styles, { before: true })} keepSize>
{before}
</Container>
)}
<Container className={s(styles, { info: true })} zeroBasis>
{children}
{description && <div className={s(styles, { description: true })}>{description}</div>}
</div>
<div className={s(styles, { after: true })}>{after}</div>
</main>
{description && <Container className={s(styles, { description: true })}>{description}</Container>}
</Container>
{after && (
<Container className={s(styles, { after: true })} keepSize>
{after}
</Container>
)}
</Container>
</div>
);
});
@@ -0,0 +1,19 @@
/*
* 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 { observer } from 'mobx-react-lite';
import { ClickableOptions, Clickable as MantineClickable } from 'reakit';
import type { ReakitProxyComponent, ReakitProxyComponentOptions } from './Menu/ReakitProxyComponent';
export const Clickable: ReakitProxyComponent<'button', ClickableOptions> = observer<ReakitProxyComponentOptions<'button', ClickableOptions>>(
function Clickable({ children, ...rest }) {
const Component = MantineClickable;
return <Component {...rest}>{children}</Component>;
},
) as ReakitProxyComponent<'button', ClickableOptions>;
@@ -0,0 +1,10 @@
/*
* 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 { importLazyComponent } from './importLazyComponent';
export const Clickable = importLazyComponent(() => import('./Clickable').then(m => m.Clickable));
@@ -7,8 +7,6 @@
*/
import { observer } from 'mobx-react-lite';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { s } from '../../s';
import { useS } from '../../useS';
import styles from './CommonDialogBody.m.css';
@@ -18,11 +16,10 @@ interface Props {
noOverflow?: boolean;
className?: string;
children?: React.ReactNode;
style?: ComponentStyle;
}
export const CommonDialogBody = observer<Props>(function CommonDialogBody({ noBodyPadding, noOverflow, className, children, style }) {
const computedStyles = useS(styles, style);
export const CommonDialogBody = observer<Props>(function CommonDialogBody({ noBodyPadding, noOverflow, className, children }) {
const computedStyles = useS(styles);
return (
<div className={s(computedStyles, { body: true, noBodyPadding, noOverflow }, className)}>
@@ -7,8 +7,6 @@
*/
import { observer } from 'mobx-react-lite';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { s } from '../../s';
import { useS } from '../../useS';
import styles from './CommonDialogFooter.m.css';
@@ -16,11 +14,10 @@ import styles from './CommonDialogFooter.m.css';
interface Props {
className?: string;
children?: React.ReactNode;
style?: ComponentStyle;
}
export const CommonDialogFooter = observer<Props>(function CommonDialogFooter({ children, className, style }) {
const computedStyles = useS(styles, style);
export const CommonDialogFooter = observer<Props>(function CommonDialogFooter({ children, className }) {
const computedStyles = useS(styles);
return <footer className={s(computedStyles, { footer: true }, className)}>{children}</footer>;
});
@@ -7,8 +7,6 @@
*/
import { observer } from 'mobx-react-lite';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { ActionIconButton } from '../../ActionIconButton';
import { IconOrImage } from '../../IconOrImage';
import { useTranslate } from '../../localization/useTranslate';
@@ -25,7 +23,6 @@ interface Props {
bigIcon?: boolean;
onReject?: () => void;
className?: string;
style?: ComponentStyle;
}
export const CommonDialogHeader = observer<Props>(function CommonDialogHeader({
@@ -37,10 +34,9 @@ export const CommonDialogHeader = observer<Props>(function CommonDialogHeader({
bigIcon,
className,
onReject,
style,
}) {
const translate = useTranslate();
const computedStyles = useS(styles, style);
const computedStyles = useS(styles);
return (
<header title={tooltip} className={s(computedStyles, { header: true }, className)}>
@@ -51,7 +47,7 @@ export const CommonDialogHeader = observer<Props>(function CommonDialogHeader({
<h3 className={s(computedStyles, { headerTitle: true })}>{translate(title)}</h3>
{onReject && (
<div className={s(computedStyles, { reject: true })}>
<ActionIconButton name="cross" viewBox="0 0 16 16" onClick={onReject} />
<ActionIconButton aria-label={translate('ui_close')} name="cross" viewBox="0 0 16 16" onClick={onReject} />
</div>
)}
</div>
@@ -1,3 +1,10 @@
/*
* 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.
*/
.container {
box-sizing: border-box;
display: flex;
@@ -17,11 +24,12 @@
max-height: 100%;
max-width: 748px;
padding: 0px;
outline: none;
&.small {
min-width: 404px;
min-height: 262px;
max-height: max(var(--app-height) - 48px, 262px);
max-height: max(var(--app-height, 100vh) - 48px, 262px);
&.fixedSize {
width: 404px;
@@ -34,7 +42,7 @@
&.medium {
min-width: 576px;
min-height: 374px;
max-height: max(var(--app-height) - 48px, 374px);
max-height: max(var(--app-height, 100vh) - 48px, 374px);
&.fixedSize {
width: 576px;
@@ -47,7 +55,7 @@
&.large {
min-width: 720px;
min-height: 468px;
max-height: max(var(--app-height) - 48px, 468px);
max-height: max(var(--app-height, 100vh) - 48px, 468px);
&.fixedSize {
width: 720px;
@@ -9,10 +9,9 @@ import { observer } from 'mobx-react-lite';
import { forwardRef, useContext, useEffect } from 'react';
import { Dialog, useDialogState } from 'reakit/Dialog';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { Loader } from '../../Loader/Loader';
import { s } from '../../s';
import { useFocus } from '../../useFocus';
import { useS } from '../../useS';
import { DialogContext } from '../DialogContext';
import styles from './CommonDialogWrapper.m.css';
@@ -23,17 +22,18 @@ export interface CommonDialogWrapperProps {
fixedSize?: boolean;
fixedWidth?: boolean;
freeHeight?: boolean;
autofocus?: boolean;
className?: string;
children?: React.ReactNode;
style?: ComponentStyle;
}
export const CommonDialogWrapper = observer<CommonDialogWrapperProps, HTMLDivElement>(
forwardRef(function CommonDialogWrapper(
{ size = 'medium', fixedSize, fixedWidth, freeHeight, 'aria-label': ariaLabel, className, children, style },
{ size = 'medium', fixedSize, fixedWidth, freeHeight, autofocus = true, 'aria-label': ariaLabel, className, children },
ref,
) {
const computedStyles = useS(styles, style);
const [focusedRef] = useFocus({ autofocus });
const computedStyles = useS(styles);
const context = useContext(DialogContext);
const dialogState = useDialogState({ visible: true });
@@ -54,6 +54,8 @@ export const CommonDialogWrapper = observer<CommonDialogWrapperProps, HTMLDivEle
modal={false}
>
<dialog
ref={focusedRef}
tabIndex={0}
className={s(
computedStyles,
{ dialog: true, small: size === 'small', medium: size === 'medium', large: size === 'large', fixedSize, fixedWidth, freeHeight },
@@ -12,7 +12,6 @@ import { Button } from '../Button';
import { Fill } from '../Fill';
import { Translate } from '../localization/Translate';
import { s } from '../s';
import { useFocus } from '../useFocus';
import { useS } from '../useS';
import { CommonDialogBody } from './CommonDialog/CommonDialogBody';
import { CommonDialogFooter } from './CommonDialog/CommonDialogFooter';
@@ -40,11 +39,10 @@ export const ConfirmationDialog: DialogComponent<ConfirmationDialogPayload, Dial
className,
}) {
const styles = useS(style);
const [focusedRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
const { icon, title, subTitle, bigIcon, viewBox, message, confirmActionText, cancelActionText } = payload;
return (
<CommonDialogWrapper ref={focusedRef} size="small" className={className} fixedWidth>
<CommonDialogWrapper size="small" className={className} fixedWidth>
<CommonDialogHeader title={title} subTitle={subTitle} icon={icon} viewBox={viewBox} bigIcon={bigIcon} onReject={rejectDialog} />
<CommonDialogBody>
<Translate token={message} />
@@ -84,8 +84,8 @@ export const DialogsPortal = observer(function DialogsPortal() {
}, [activeDialog]);
return (
<Loader className={s(styles, { loader: true })} suspense overlay>
<DialogBackdrop className={s(styles, { backdrop: true })} visible={!!activeDialog} onMouseDown={state.backdropClick}>
<DialogBackdrop className={s(styles, { backdrop: true })} visible={!!activeDialog} onMouseDown={state.backdropClick}>
<Loader className={s(styles, { loader: true })} suspense>
<div className={s(styles, { innerBox: true })}>
{commonDialogService.dialogs.map((dialog, i, arr) => (
<ErrorBoundary key={dialog.id} className={s(styles, { error: true })} remount onClose={state.reject}>
@@ -99,8 +99,8 @@ export const DialogsPortal = observer(function DialogsPortal() {
</ErrorBoundary>
))}
</div>
</DialogBackdrop>
</Loader>
</Loader>
</DialogBackdrop>
);
});
@@ -14,16 +14,21 @@ import { filterContainerFakeProps, getContainerProps } from './filterContainerFa
import type { IContainerProps } from './IContainerProps';
import elementsSizeStyle from './shared/ElementsSize.m.css';
export const Container = forwardRef<HTMLDivElement, IContainerProps & React.HTMLAttributes<HTMLDivElement>>(function Container(
{ className, ...rest },
interface Props {
as?: 'div' | 'header' | 'footer' | 'section' | 'aside' | 'main' | 'nav';
}
export const Container = forwardRef<HTMLDivElement, Props & IContainerProps & React.HTMLAttributes<HTMLDivElement>>(function Container(
{ as = 'div', className, ...rest },
ref,
) {
const styles = useS(style, elementsSizeStyle);
const divProps = filterContainerFakeProps(rest);
const containerProps = getContainerProps(rest);
const Element = as;
return (
<div
<Element
ref={ref}
{...divProps}
className={s(
@@ -12,6 +12,10 @@
.surface {
composes: theme-background-surface theme-text-on-surface from global;
}
.border {
composes: theme-border-color-background from global;
border: 1px solid;
}
.group {
align-content: baseline;
box-sizing: border-box;
@@ -49,14 +53,6 @@
right: 24px;
}
}
&.box.dense > .groupTitle {
padding: 8px;
margin: 0;
& > .groupClose {
right: 8px;
}
}
&.box,
&.box.dense,
@@ -20,12 +20,13 @@ interface Props extends IContainerProps {
secondary?: boolean;
center?: boolean;
box?: boolean;
border?: boolean;
hideOverflow?: boolean;
boxNoOverflow?: boolean;
}
export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTMLDivElement>>(function Group(
{ form, center, box, secondary, boxNoOverflow, hideOverflow, className, ...rest },
{ form, center, box, border, secondary, boxNoOverflow, hideOverflow, className, ...rest },
ref,
) {
const styles = useS(style, containerStyles, elementsSizeStyles);
@@ -51,6 +52,7 @@ export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTM
boxNoOverflow,
hideOverflow,
box,
border,
},
className,
)}
@@ -1,3 +1,10 @@
/*
* 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.
*/
.error {
width: 100%;
height: 100%;
@@ -5,7 +12,7 @@
overflow: auto;
&.root {
height: var(--app-height);
height: var(--app-height, 100vh);
}
}
.errorInnerBlock {
@@ -29,7 +29,7 @@ export const DisplayError = observer<Props>(function DisplayError({ root, childr
const stack = errorInfo?.componentStack || error?.stack;
return (
<div className={s(styles, { error: true, root }, className)}>
<div role="alert" tabIndex={0} className={s(styles, { error: true, root }, className)}>
<div className={s(styles, { errorInnerBlock: true })}>
<NotificationMark className={s(styles, { notificationMark: true })} type={ENotificationType.Error} />
<p>Something went wrong.</p>
@@ -23,7 +23,6 @@ import { useClipboard } from '../useClipboard';
import { useS } from '../useS';
import style from './ErrorDetailsDialog.m.css';
import { ErrorModel, IErrorInfo } from './ErrorModel';
import { useFocus } from '../useFocus';
function DisplayErrorInfo({ error }: { error: IErrorInfo }) {
const styles = useS(style);
@@ -43,7 +42,6 @@ function DisplayErrorInfo({ error }: { error: IErrorInfo }) {
}
export const ErrorDetailsDialog: DialogComponent<Error | string, null> = observer(function ErrorDetailsDialog(props) {
const [focusedRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
const translate = useTranslate();
const styles = useS(style);
@@ -53,7 +51,7 @@ export const ErrorDetailsDialog: DialogComponent<Error | string, null> = observe
const copyHandler = useCallback(() => copy(error.textToCopy, true), [error, copy]);
return (
<CommonDialogWrapper ref={focusedRef} size="large">
<CommonDialogWrapper size="large">
<CommonDialogHeader title="core_eventsLog_dbeaverErrorDetails" icon="/icons/error_icon.svg" bigIcon onReject={props.rejectDialog} />
<CommonDialogBody>
{error.errors.map((error, id) => (
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import '@testing-library/jest-dom';
import { screen } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import { coreEventsManifest } from '@cloudbeaver/core-events';
import { coreLocalizationManifest } from '@cloudbeaver/core-localization';
@@ -18,7 +18,7 @@ import { ErrorMessage } from './ErrorMessage';
const app = createApp(coreEventsManifest, coreSettingsManifest, coreThemingManifest, coreLocalizationManifest);
test('icons.svg#name', () => {
test('icons.svg#name', async () => {
renderInApp(<ErrorMessage text="error" />, app);
expect(screen.getByText('error')).not.toBeNull();
await waitFor(() => expect(screen.getByText('error')).not.toBeNull());
});
@@ -6,13 +6,13 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { Button } from './Button';
import styles from './ErrorMessage.m.css';
import { IconOrImage } from './IconOrImage';
import { useTranslate } from './localization/useTranslate';
import { useS } from './useS';
import styles from './ErrorMessage.m.css';
import { s } from './s';
import { useS } from './useS';
interface Props {
hasDetails?: boolean;
@@ -26,9 +26,11 @@ export const ErrorMessage = observer<Props>(function ErrorMessage({ text, classN
const style = useS(styles);
return (
<div className={s(style, { message: true }, className)}>
<div role="status" tabIndex={0} aria-label={text} className={s(style, { message: true }, className)}>
<IconOrImage className={s(style, { errorIcon: true })} icon="/icons/error_icon_sm.svg" />
<div className={s(style, { messageBody: true })} title={text}>{text}</div>
<div className={s(style, { messageBody: true })} title={text}>
{text}
</div>
<div className={s(style, { messageActions: true })}>
{hasDetails && (
<Button type="button" mod={['outlined']} onClick={onShowDetails}>
@@ -7,8 +7,6 @@
*/
import { observer } from 'mobx-react-lite';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { isControlPresented } from '../isControlPresented';
import { CheckboxMarkup, CheckboxMod } from './CheckboxMarkup';
import { CheckboxOnChangeEvent, useCheckboxState } from './useCheckboxState';
@@ -19,7 +17,6 @@ export interface CheckboxBaseProps {
ripple?: boolean;
indeterminate?: boolean;
inverse?: boolean;
style?: ComponentStyle;
}
export type CheckboxInputProps = Omit<

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