mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6416486513 | ||
|
|
6061ec340c | ||
|
|
b40646e69b | ||
|
|
003a89be57 | ||
|
|
bbd296f197 | ||
|
|
39bfc54ca5 | ||
|
|
bac47d20ce | ||
|
|
5fce0a544b | ||
|
|
833dae58ce | ||
|
|
12bc21257d | ||
|
|
d280171b97 | ||
|
|
c8ec5716b8 | ||
|
|
7045575d95 | ||
|
|
bdea460218 | ||
|
|
674c522d55 | ||
|
|
d0933b2b17 | ||
|
|
56143132d6 | ||
|
|
639f42fa1c | ||
|
|
c0bbe7fdc5 | ||
|
|
64103bc369 | ||
|
|
87e7f774e9 | ||
|
|
00cb84c71d |
@@ -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: .
|
||||
|
||||
@@ -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>
|
||||
|
||||
+8
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+8
-2
@@ -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:
|
||||
|
||||
+3
-18
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-11
@@ -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;
|
||||
|
||||
+19
-8
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
+5
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
+10
@@ -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(
|
||||
|
||||
+20
@@ -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 {
|
||||
|
||||
+16
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
@@ -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>
|
||||
|
||||
+9
-10
@@ -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>
|
||||
|
||||
+144
-41
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -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: .
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ import java.nio.file.Path;
|
||||
@Suite.SuiteClasses(
|
||||
{
|
||||
ConnectionsTest.class,
|
||||
SQLQueryTranslatorTest.class,
|
||||
AuthenticationTest.class,
|
||||
ResourceManagerTest.class,
|
||||
RMLockTest.class,
|
||||
|
||||
+200
@@ -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
@@ -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),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
+11
-3
@@ -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
Reference in New Issue
Block a user