mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into feat/CB-2302/dnd-folders
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
name: Run Jest Tests
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the "devel" branch
|
||||
pull_request:
|
||||
branches: [ "devel" ]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
run-jest-tests:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./webapp
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- run: npm install --legacy-peer-deps
|
||||
- run: npm install lerna -g
|
||||
- run: lerna bootstrap
|
||||
- run: lerna run test --no-bail --stream -- --reporters default github-actions
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.model;singleton:=true
|
||||
Bundle-Version: 1.0.11.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.12.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.model</artifactId>
|
||||
<version>1.0.11-SNAPSHOT</version>
|
||||
<version>1.0.12-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ public abstract class AbstractSessionFederated implements SMSessionFederated {
|
||||
return parentSession.getSingletonProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
+17
@@ -24,12 +24,14 @@ import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBIcon;
|
||||
import org.jkiss.dbeaver.model.DBPImage;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
import org.jkiss.dbeaver.model.rm.RMProject;
|
||||
import org.jkiss.dbeaver.model.rm.RMResource;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -114,6 +116,21 @@ public class DBNResourceManagerProject extends DBNAbstractResourceManagerNode {
|
||||
return getNodeName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DBPProject getOwnerProject() {
|
||||
List<? extends DBPProject> globalProjects = getModel().getModelProjects();
|
||||
if (globalProjects == null) {
|
||||
return null;
|
||||
}
|
||||
for (DBPProject modelProject : globalProjects) {
|
||||
if (CommonUtils.equalObjects(modelProject.getId(), project.getId())) {
|
||||
return modelProject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DBPObject getObjectDetails(@NotNull DBRProgressMonitor monitor, @NotNull SMSessionContext sessionContext, @NotNull Object dataSource) throws DBException {
|
||||
|
||||
+6
@@ -24,6 +24,7 @@ import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.DBIcon;
|
||||
import org.jkiss.dbeaver.model.DBPImage;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.app.DBPProject;
|
||||
import org.jkiss.dbeaver.model.auth.SMSessionContext;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.rm.RMController;
|
||||
@@ -165,6 +166,11 @@ public class DBNResourceManagerResource extends DBNAbstractResourceManagerNode {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBPProject getOwnerProject() {
|
||||
return getParentNode().getOwnerProject();
|
||||
}
|
||||
|
||||
public RMResource getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
+1
-1
@@ -545,7 +545,7 @@ public class LocalResourceController implements RMController {
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
project.setCreateTime(
|
||||
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")));
|
||||
OffsetDateTime.ofInstant(Files.getLastModifiedTime(path).toInstant(), ZoneId.of("UTC")).toInstant().toEpochMilli());
|
||||
} catch (IOException e) {
|
||||
log.error(e);
|
||||
}
|
||||
|
||||
+13
-2
@@ -584,14 +584,19 @@ public class WebSession extends AbstractSessionPersistent implements SMSession,
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws DBException {
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
resetNavigationModel();
|
||||
resetSessionCache();
|
||||
} catch (Throwable e) {
|
||||
log.error(e);
|
||||
}
|
||||
clearAuthTokens();
|
||||
try {
|
||||
clearAuthTokens();
|
||||
} catch (Exception e) {
|
||||
log.error("Error closing web session tokens");
|
||||
}
|
||||
this.sessionAuthContext.close();
|
||||
this.userContext.setUser(null);
|
||||
|
||||
@@ -599,6 +604,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession,
|
||||
this.defaultProject.dispose();
|
||||
this.defaultProject = null;
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
|
||||
private void clearAuthTokens() throws DBException {
|
||||
@@ -959,6 +965,11 @@ public class WebSession extends AbstractSessionPersistent implements SMSession,
|
||||
return userContext.getActiveUserCredentials();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSMSession() throws DBException {
|
||||
userContext.refreshSMSession();
|
||||
}
|
||||
|
||||
public VirtualProjectImpl getProjectById(@Nullable String projectId) {
|
||||
if (projectId == null) {
|
||||
return defaultProject;
|
||||
|
||||
+24
-5
@@ -47,6 +47,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
private Set<String> userPermissions;
|
||||
private SMCredentials smCredentials = null;
|
||||
private String smSessionId;
|
||||
private String refreshToken;
|
||||
|
||||
private SMController securityController;
|
||||
private SMAdminController adminSecurityController;
|
||||
@@ -64,7 +65,7 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
* @param smAuthInfo - auth info from security manager
|
||||
* @throws DBException - if user already authorized and new token come from another user
|
||||
*/
|
||||
public void refresh(SMAuthInfo smAuthInfo) throws DBException {
|
||||
public synchronized void refresh(SMAuthInfo smAuthInfo) throws DBException {
|
||||
if (smAuthInfo.getAuthStatus() != SMAuthStatus.SUCCESS) {
|
||||
throw new DBCException("Authorization did not complete successfully");
|
||||
}
|
||||
@@ -77,7 +78,12 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
if (isNonAnonymousUserAuthorized && isSessionChanged && !Objects.equals(getUserId(), authPermissions.getUserId())) {
|
||||
throw new DBCException("Another user is already logged in");
|
||||
}
|
||||
this.smCredentials = new SMCredentials(smAuthInfo.getSmAuthToken(), authPermissions.getUserId(), authPermissions.getPermissions());
|
||||
this.smCredentials = new SMCredentials(
|
||||
smAuthInfo.getSmAuthToken(),
|
||||
authPermissions.getUserId(),
|
||||
authPermissions.getPermissions()
|
||||
);
|
||||
this.refreshToken = smAuthInfo.getSmRefreshToken();
|
||||
this.userPermissions = authPermissions.getPermissions();
|
||||
this.securityController = application.getSecurityController(this);
|
||||
this.adminSecurityController = application.getAdminSecurityController(this);
|
||||
@@ -89,10 +95,23 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
|
||||
}
|
||||
|
||||
public synchronized void refreshSMSession() throws DBException {
|
||||
if (smCredentials == null || refreshToken == null) {
|
||||
return;
|
||||
}
|
||||
var newTokens = securityController.refreshSession(refreshToken);
|
||||
this.refreshToken = newTokens.getSmRefreshToken();
|
||||
this.smCredentials = new SMCredentials(
|
||||
newTokens.getSmAccessToken(),
|
||||
smCredentials.getUserId(),
|
||||
smCredentials.getPermissions()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* reset the state as if the user is not logged in
|
||||
*/
|
||||
public void reset() throws DBException {
|
||||
public synchronized void reset() throws DBException {
|
||||
if (this.smCredentials != null) {
|
||||
this.securityController.logout();
|
||||
}
|
||||
@@ -155,8 +174,8 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
}
|
||||
if (userPermissions != null &&
|
||||
!userPermissions.isEmpty() &&
|
||||
!userPermissions.contains(DBWConstants.PERMISSION_PUBLIC))
|
||||
{
|
||||
!userPermissions.contains(DBWConstants.PERMISSION_PUBLIC)
|
||||
) {
|
||||
userPermissions.add(DBWConstants.PERMISSION_PUBLIC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.product.ce;singleton:=true
|
||||
Bundle-Version: 22.1.5.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 22.2.0.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce</artifactId>
|
||||
<version>22.1.5-SNAPSHOT</version>
|
||||
<version>22.2.0-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.56.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.57.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
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.56-SNAPSHOT</version>
|
||||
<version>1.0.57-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.server;singleton:=true
|
||||
Bundle-Version: 22.1.5.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 22.2.0.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-Activator: io.cloudbeaver.server.CBPlatformActivator
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server</artifactId>
|
||||
<version>22.1.5-SNAPSHOT</version>
|
||||
<version>22.2.0-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+1
-5
@@ -181,11 +181,7 @@ public class WebSessionManager {
|
||||
|
||||
for (WebSession session : expiredList) {
|
||||
log.debug("> Expire session '" + session.getSessionId() + "'");
|
||||
try {
|
||||
session.close();
|
||||
} catch (DBException e) {
|
||||
log.debug("Failed close session:" + session.getSessionId(), e);
|
||||
}
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.admin;singleton:=true
|
||||
Bundle-Version: 1.0.55.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.56.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.admin</artifactId>
|
||||
<version>1.0.55-SNAPSHOT</version>
|
||||
<version>1.0.56-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.auth;singleton:=true
|
||||
Bundle-Version: 1.0.55.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.56.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.auth</artifactId>
|
||||
<version>1.0.55-SNAPSHOT</version>
|
||||
<version>1.0.56-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.data.transfer;singleton:=true
|
||||
Bundle-Version: 1.0.56.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.57.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.data.transfer</artifactId>
|
||||
<version>1.0.56-SNAPSHOT</version>
|
||||
<version>1.0.57-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.metadata;singleton:=true
|
||||
Bundle-Version: 1.0.59.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.60.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.metadata</artifactId>
|
||||
<version>1.0.59-SNAPSHOT</version>
|
||||
<version>1.0.60-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: %Bundle-Vendor
|
||||
Bundle-Name: %Bundle-Name
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.rm;singleton:=true
|
||||
Bundle-Version: 1.0.8.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.9.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.rm</artifactId>
|
||||
<version>1.0.8-SNAPSHOT</version>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,8 +3,8 @@ Bundle-ManifestVersion: 2
|
||||
Bundle-Vendor: DBeaver Security Sevice
|
||||
Bundle-Vendor: DBeaver Corp
|
||||
Bundle-SymbolicName: io.cloudbeaver.service.security;singleton:=true
|
||||
Bundle-Version: 1.0.11.qualifier
|
||||
Bundle-Release-Date: 20220822
|
||||
Bundle-Version: 1.0.12.qualifier
|
||||
Bundle-Release-Date: 20220905
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-11
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-ClassPath: .
|
||||
|
||||
@@ -238,12 +238,14 @@ CREATE TABLE CB_SESSION_LOG
|
||||
|
||||
CREATE TABLE CB_AUTH_TOKEN
|
||||
(
|
||||
TOKEN_ID VARCHAR(128) NOT NULL,
|
||||
SESSION_ID VARCHAR(64) NOT NULL,
|
||||
USER_ID VARCHAR(128),
|
||||
TOKEN_ID VARCHAR(128) NOT NULL,
|
||||
REFRESH_TOKEN_ID VARCHAR(128),
|
||||
SESSION_ID VARCHAR(64) NOT NULL,
|
||||
USER_ID VARCHAR(128),
|
||||
|
||||
EXPIRATION_TIME TIMESTAMP NOT NULL,
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
EXPIRATION_TIME TIMESTAMP NOT NULL,
|
||||
REFRESH_TOKEN_EXPIRATION_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (TOKEN_ID),
|
||||
FOREIGN KEY (SESSION_ID) REFERENCES CB_SESSION (SESSION_ID) ON DELETE CASCADE,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE CB_AUTH_TOKEN
|
||||
ADD REFRESH_TOKEN_ID VARCHAR(128);
|
||||
|
||||
ALTER TABLE CB_AUTH_TOKEN
|
||||
ADD REFRESH_TOKEN_EXPIRATION_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.service.security</artifactId>
|
||||
<version>1.0.11-SNAPSHOT</version>
|
||||
<version>1.0.12-SNAPSHOT</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
</project>
|
||||
|
||||
+5
@@ -76,4 +76,9 @@ public class LocalAuthSession implements SMSession {
|
||||
return webSession.getSingletonProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+105
-27
@@ -27,6 +27,7 @@ import io.cloudbeaver.model.app.WebAuthConfiguration;
|
||||
import io.cloudbeaver.model.session.WebAuthInfo;
|
||||
import io.cloudbeaver.service.security.db.CBDatabase;
|
||||
import io.cloudbeaver.service.security.internal.AuthAttemptSessionInfo;
|
||||
import io.cloudbeaver.service.security.internal.RefreshTokenInfo;
|
||||
import io.cloudbeaver.utils.WebAppUtils;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
@@ -39,7 +40,9 @@ import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
|
||||
import org.jkiss.dbeaver.model.security.*;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMException;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMRefreshTokenExpiredException;
|
||||
import org.jkiss.dbeaver.model.security.user.SMAuthPermissions;
|
||||
import org.jkiss.dbeaver.model.security.user.SMObjectPermissions;
|
||||
import org.jkiss.dbeaver.model.security.user.SMRole;
|
||||
@@ -65,7 +68,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
|
||||
private static final Log log = Log.getLog(CBEmbeddedSecurityController.class);
|
||||
|
||||
private static final int TOKEN_HOURS_ALIVE_TIME = 1;
|
||||
private static final int ACCESS_TOKEN_TTL_IN_MINUTES = 20;
|
||||
private static final int REFRESH_TOKEN_TTL_IN_HOURS = 24;
|
||||
|
||||
protected static final String CHAR_BOOL_TRUE = "Y";
|
||||
protected static final String CHAR_BOOL_FALSE = "N";
|
||||
@@ -844,10 +848,15 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
var smSessionId = createSmSession(appSessionId, null, sessionParameters, sessionType, dbCon);
|
||||
var token = generateAuthToken(smSessionId, null, dbCon);
|
||||
var smTokens = generateNewSessionToken(smSessionId, null, dbCon);
|
||||
var permissions = getAnonymousUserPermissions();
|
||||
txn.commit();
|
||||
return SMAuthInfo.success(UUID.randomUUID().toString(), token, new SMAuthPermissions(null, smSessionId, permissions), Map.of());
|
||||
return SMAuthInfo.success(
|
||||
UUID.randomUUID().toString(),
|
||||
smTokens.getSmAccessToken(),
|
||||
smTokens.getSmRefreshToken(),
|
||||
new SMAuthPermissions(null, smSessionId, permissions), Map.of()
|
||||
);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e.getMessage(), e);
|
||||
@@ -1104,9 +1113,15 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
}
|
||||
}
|
||||
|
||||
String smToken = findTokenBySmSession(smSessionId);
|
||||
SMAuthPermissions authPermissions = getTokenPermissions(smToken);
|
||||
var successAuthStatus = SMAuthInfo.success(authId, smToken, authPermissions, authData);
|
||||
SMTokens smTokens = findTokenBySmSession(smSessionId);
|
||||
SMAuthPermissions authPermissions = getTokenPermissions(smTokens.getSmAccessToken());
|
||||
var successAuthStatus = SMAuthInfo.success(
|
||||
authId,
|
||||
smTokens.getSmAccessToken(),
|
||||
smTokens.getSmRefreshToken(),
|
||||
authPermissions,
|
||||
authData
|
||||
);
|
||||
updateAuthStatus(authId, SMAuthStatus.EXPIRED, authData, null, authPermissions.getSessionId());
|
||||
return successAuthStatus;
|
||||
|
||||
@@ -1118,10 +1133,29 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
@Override
|
||||
public void logout() throws DBException {
|
||||
var currentUserCreds = getCurrentUserCreds();
|
||||
invalidateUserSession(currentUserCreds.getSmToken());
|
||||
invalidateUserTokens(currentUserCreds.getSmToken());
|
||||
}
|
||||
|
||||
private void invalidateUserSession(String smToken) throws DBCException {
|
||||
@Override
|
||||
public SMTokens refreshSession(@NotNull String refreshToken) throws DBException {
|
||||
var currentUserCreds = getCurrentUserCreds();
|
||||
var currentUserAccessToken = currentUserCreds.getSmToken();
|
||||
|
||||
var expectedRefreshTokenInfo = findRefreshToken(currentUserAccessToken);
|
||||
|
||||
if (!expectedRefreshTokenInfo.getRefreshToken().equals(refreshToken)) {
|
||||
throw new SMException("Invalid refresh token");
|
||||
}
|
||||
|
||||
try (var dbCon = database.openConnection()) {
|
||||
invalidateUserTokens(currentUserAccessToken);
|
||||
return generateNewSessionToken(expectedRefreshTokenInfo.getSessionId(), expectedRefreshTokenInfo.getUserId(), dbCon);
|
||||
} catch (SQLException e) {
|
||||
throw new DBException("Error refreshing sm session", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateUserTokens(String smToken) throws DBCException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
JDBCUtils.executeStatement(dbCon, "DELETE FROM CB_AUTH_TOKEN WHERE TOKEN_ID=?", smToken);
|
||||
} catch (SQLException e) {
|
||||
@@ -1137,16 +1171,41 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
return currentUserCreds;
|
||||
}
|
||||
|
||||
private String findTokenBySmSession(String smSessionId) throws DBException {
|
||||
private SMTokens findTokenBySmSession(String smSessionId) throws DBException {
|
||||
try (Connection dbCon = database.openConnection();
|
||||
PreparedStatement dbStat = dbCon.prepareStatement("SELECT TOKEN_ID FROM CB_AUTH_TOKEN WHERE SESSION_ID=?");
|
||||
PreparedStatement dbStat = dbCon.prepareStatement("SELECT TOKEN_ID, REFRESH_TOKEN_ID FROM CB_AUTH_TOKEN WHERE SESSION_ID=?")
|
||||
) {
|
||||
dbStat.setString(1, smSessionId);
|
||||
try (var dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Token not found");
|
||||
}
|
||||
return dbResult.getString(1);
|
||||
return new SMTokens(dbResult.getString(1), dbResult.getString(2));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error reading token info in database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private RefreshTokenInfo findRefreshToken(String smAccessToken) throws DBException {
|
||||
try (Connection dbCon = database.openConnection();
|
||||
PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT REFRESH_TOKEN_ID,SESSION_ID,USER_ID,REFRESH_TOKEN_EXPIRATION_TIME FROM CB_AUTH_TOKEN WHERE TOKEN_ID=?"
|
||||
)
|
||||
) {
|
||||
dbStat.setString(1, smAccessToken);
|
||||
try (var dbResult = dbStat.executeQuery()) {
|
||||
if (!dbResult.next()) {
|
||||
throw new SMException("Refresh token not found");
|
||||
}
|
||||
var refreshToken = dbResult.getString(1);
|
||||
var sessionId = dbResult.getString(2);
|
||||
var userId = dbResult.getString(3);
|
||||
var expiredDate = dbResult.getTimestamp(4);
|
||||
if (Timestamp.from(Instant.now()).after(expiredDate)) {
|
||||
throw new SMRefreshTokenExpiredException("Refresh token expired");
|
||||
}
|
||||
return new RefreshTokenInfo(refreshToken, sessionId, userId);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error reading token info in database", e);
|
||||
@@ -1173,12 +1232,12 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
AuthAttemptSessionInfo authAttemptSessionInfo = readAuthAttemptSessionInfo(authId);
|
||||
boolean isMainAuthSession = authAttemptSessionInfo.getSmSessionId() == null;
|
||||
|
||||
String token = null;
|
||||
SMTokens smTokens = null;
|
||||
SMAuthPermissions permissions = null;
|
||||
if (!isMainAuthSession) {
|
||||
//this is an additional authorization and we should to return the original permissions and userId
|
||||
token = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId());
|
||||
permissions = getTokenPermissions(token);
|
||||
smTokens = findTokenBySmSession(authAttemptSessionInfo.getSmSessionId());
|
||||
permissions = getTokenPermissions(smTokens.getSmAccessToken());
|
||||
}
|
||||
String activeUserId = permissions == null ? null : permissions.getUserId();
|
||||
|
||||
@@ -1208,7 +1267,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
);
|
||||
}
|
||||
|
||||
if (token == null && permissions == null) {
|
||||
if (smTokens == null && permissions == null) {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (JDBCTransaction txn = new JDBCTransaction(dbCon)) {
|
||||
String smSessionId;
|
||||
@@ -1223,7 +1282,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
} else {
|
||||
smSessionId = authAttemptSessionInfo.getSmSessionId();
|
||||
}
|
||||
token = generateAuthToken(smSessionId, activeUserId, dbCon);
|
||||
smTokens = generateNewSessionToken(smSessionId, activeUserId, dbCon);
|
||||
permissions = new SMAuthPermissions(activeUserId, smSessionId, getUserPermissions(activeUserId));
|
||||
txn.commit();
|
||||
}
|
||||
@@ -1235,7 +1294,14 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
}
|
||||
var authStatus = forceExpireAuthAfterSuccess ? SMAuthStatus.EXPIRED : SMAuthStatus.SUCCESS;
|
||||
updateAuthStatus(authId, authStatus, storedUserData, null, permissions.getSessionId());
|
||||
return SMAuthInfo.success(authId, token, permissions, authInfo.getAuthData());
|
||||
return SMAuthInfo.success(
|
||||
authId,
|
||||
smTokens.getSmAccessToken(),
|
||||
//refresh token must be sent only once
|
||||
isMainAuthSession ? smTokens.getSmRefreshToken() : null,
|
||||
permissions,
|
||||
authInfo.getAuthData()
|
||||
);
|
||||
}
|
||||
|
||||
private AuthAttemptSessionInfo readAuthAttemptSessionInfo(@NotNull String authId) throws DBException {
|
||||
@@ -1316,28 +1382,40 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
return userId;
|
||||
}
|
||||
|
||||
protected String generateAuthToken(
|
||||
protected SMTokens generateNewSessionToken(
|
||||
@NotNull String smSessionId,
|
||||
@Nullable String userId,
|
||||
@NotNull Connection dbCon
|
||||
) throws SQLException, DBException {
|
||||
JDBCUtils.executeStatement(dbCon, "DELETE FROM CB_AUTH_TOKEN WHERE SESSION_ID=?", smSessionId);
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO CB_AUTH_TOKEN(TOKEN_ID, SESSION_ID, USER_ID, EXPIRATION_TIME) " +
|
||||
"VALUES(?,?,?,?)")) {
|
||||
return generateNewSessionTokens(smSessionId, userId, dbCon);
|
||||
}
|
||||
|
||||
String authToken = SecurityUtils.generatePassword(32);
|
||||
dbStat.setString(1, authToken);
|
||||
private SMTokens generateNewSessionTokens(@NotNull String smSessionId,
|
||||
@Nullable String userId,
|
||||
@NotNull Connection dbCon) throws SQLException {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"INSERT INTO CB_AUTH_TOKEN(TOKEN_ID,SESSION_ID,USER_ID,EXPIRATION_TIME,REFRESH_TOKEN_ID,REFRESH_TOKEN_EXPIRATION_TIME) " +
|
||||
"VALUES(?,?,?,?,?,?)")) {
|
||||
|
||||
String smAccessToken = SecurityUtils.generatePassword(32);
|
||||
dbStat.setString(1, smAccessToken);
|
||||
dbStat.setString(2, smSessionId);
|
||||
if (userId == null) {
|
||||
dbStat.setNull(3, Types.VARCHAR);
|
||||
} else {
|
||||
dbStat.setString(3, userId);
|
||||
}
|
||||
var expirationTime = Timestamp.valueOf(LocalDateTime.now().plusHours(TOKEN_HOURS_ALIVE_TIME));
|
||||
dbStat.setTimestamp(4, expirationTime);
|
||||
var accessTokenExpirationTime = Timestamp.valueOf(LocalDateTime.now().plusMinutes(ACCESS_TOKEN_TTL_IN_MINUTES));
|
||||
dbStat.setTimestamp(4, accessTokenExpirationTime);
|
||||
|
||||
String smRefreshToken = SecurityUtils.generatePassword(32);
|
||||
dbStat.setString(5, smRefreshToken);
|
||||
var refreshTokenExpirationTime = Timestamp.valueOf(LocalDateTime.now().plusHours(REFRESH_TOKEN_TTL_IN_HOURS));
|
||||
dbStat.setTimestamp(6, refreshTokenExpirationTime);
|
||||
|
||||
dbStat.execute();
|
||||
return authToken;
|
||||
return new SMTokens(smAccessToken, smRefreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1356,7 +1434,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
userId = dbResult.getString(1);
|
||||
var expiredDate = dbResult.getTimestamp(2);
|
||||
if (application.isMultiNode() && Timestamp.from(Instant.now()).after(expiredDate)) {
|
||||
throw new SMException("Token expired");
|
||||
throw new SMAccessTokenExpiredException("Token expired");
|
||||
}
|
||||
sessionId = dbResult.getString(3);
|
||||
}
|
||||
|
||||
+1
-1
@@ -68,7 +68,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 = 8;
|
||||
private static final int CURRENT_SCHEMA_VERSION = 9;
|
||||
|
||||
private static final String DEFAULT_DB_USER_NAME = "cb-data";
|
||||
private static final String DEFAULT_DB_PWD_FILE = ".database-credentials.dat";
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2022 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.security.internal;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
|
||||
public class RefreshTokenInfo {
|
||||
@NotNull
|
||||
private final String refreshToken;
|
||||
|
||||
@NotNull
|
||||
private final String sessionId;
|
||||
|
||||
@NotNull
|
||||
private final String userId;
|
||||
|
||||
public RefreshTokenInfo(@NotNull String refreshToken, @NotNull String sessionId, @NotNull String userId) {
|
||||
this.refreshToken = refreshToken;
|
||||
this.sessionId = sessionId;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.ce.drivers.feature"
|
||||
label="Cloudbeaver CE 3rd Party Drivers"
|
||||
version="1.0.79.qualifier"
|
||||
version="1.0.80.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.79-SNAPSHOT</version>
|
||||
<version>1.0.80-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.product.ce.feature"
|
||||
label="%featureName"
|
||||
version="22.1.5.qualifier"
|
||||
version="22.2.0.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.product.ce">
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.product.ce.feature</artifactId>
|
||||
<version>22.1.5-SNAPSHOT</version>
|
||||
<version>22.2.0-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.server.feature"
|
||||
label="%featureName"
|
||||
version="22.1.5.qualifier"
|
||||
version="22.2.0.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.server">
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.server.feature</artifactId>
|
||||
<version>22.1.5-SNAPSHOT</version>
|
||||
<version>22.2.0-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<feature
|
||||
id="io.cloudbeaver.ws.feature"
|
||||
label="%featureName"
|
||||
version="1.0.9.qualifier"
|
||||
version="1.0.10.qualifier"
|
||||
provider-name="%providerName"
|
||||
plugin="io.cloudbeaver.model">
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
<artifactId>io.cloudbeaver.ws.feature</artifactId>
|
||||
<version>1.0.9-SNAPSHOT</version>
|
||||
<version>1.0.10-SNAPSHOT</version>
|
||||
<packaging>eclipse-feature</packaging>
|
||||
</project>
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
|
||||
<properties>
|
||||
<dbeaver-product>CloudBeaver CE</dbeaver-product>
|
||||
<dbeaver-version>22.1.5</dbeaver-version>
|
||||
<dbeaver-version>22.2.0</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="22.1.5.qualifier" useFeatures="true"
|
||||
application="io.cloudbeaver.product.ce.application" version="22.2.0.qualifier" useFeatures="true"
|
||||
includeLaunchers="false">
|
||||
|
||||
<configIni use="default">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../</relativePath>
|
||||
</parent>
|
||||
<version>22.1.5-SNAPSHOT</version>
|
||||
<version>22.2.0-SNAPSHOT</version>
|
||||
<artifactId>web-server</artifactId>
|
||||
<packaging>eclipse-repository</packaging>
|
||||
<name>Cloudbeaver Server Product</name>
|
||||
|
||||
@@ -10,35 +10,6 @@ export default [
|
||||
['app_shared_settingsMenu_theme', 'Theme'],
|
||||
['app_shared_settingsMenu_lang', 'Language'],
|
||||
['app_topnavbar_connection_schema_manager_not_selected', '-- not selected --'],
|
||||
['app_navigationTree_openNodeTab', 'Open'],
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view', 'Connection view'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Simple'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Advanced'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Custom'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Show system objects'],
|
||||
['app_navigationTree_refresh', 'Refresh'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_node_change_name_error', 'Error occurred while changing name'],
|
||||
['app_navigationTree_context_disconnect', 'Disconnect'],
|
||||
['app_navigationTree_connection_group_user', 'User Connections'],
|
||||
['app_navigationTree_connection_group_shared', 'Shared Connections'],
|
||||
['app_shared_inlineEditor_dialog_title', 'Edit mode'],
|
||||
['app_shared_inlineEditor_dialog_apply', 'Apply'],
|
||||
['app_shared_inlineEditor_dialog_cancel', 'Cancel'],
|
||||
|
||||
@@ -10,34 +10,6 @@ export default [
|
||||
['app_shared_settingsMenu_theme', 'Tema'],
|
||||
['app_shared_settingsMenu_lang', 'Lingua'],
|
||||
['app_topnavbar_connection_schema_manager_not_selected', '-- non selezionato --'],
|
||||
['app_navigationTree_openNodeTab', 'Apri'],
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view', 'Vista di connessione'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Semplice'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Avanzato'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Manuale'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Mostra oggetti di sistema'],
|
||||
['app_navigationTree_refresh', 'Aggiorna'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_context_disconnect', 'Scollega'],
|
||||
['app_navigationTree_connection_group_user', 'Connessioni Utente'],
|
||||
['app_navigationTree_connection_group_shared', 'Connessioni Condivise'],
|
||||
['app_shared_inlineEditor_dialog_title', 'Modalità modifica'],
|
||||
['app_shared_inlineEditor_dialog_apply', 'Applica'],
|
||||
['app_shared_inlineEditor_dialog_cancel', 'Cancella'],
|
||||
|
||||
@@ -13,38 +13,6 @@ export default [
|
||||
'app_shared_navigationTabsBar_placeholder',
|
||||
'Нет открытых объектов для отображения. Дважды нажмите на элемент в дереве навигации, чтобы открыть его.',
|
||||
],
|
||||
['app_navigationTree_openNodeTab', 'Открыть'],
|
||||
['app_navigationTree_search', 'Искать элементы...'],
|
||||
['app_navigationTree_limited', 'Отображено {args.limit} элементов'],
|
||||
|
||||
['app_navigationTree_action_link_with_editor', 'Синхронизовать с редактором'],
|
||||
['app_navigationTree_action_collapse_all', 'Свернуть все элементы'],
|
||||
|
||||
['app_navigationTree_settings_filter_title', 'Фильтр'],
|
||||
['app_navigationTree_settings_filter_description', 'Показывать фильтр'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Фильтровать скрытые элементы'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Показывает скрытые элементы при использовании фильтра'],
|
||||
['app_navigationTree_settings_state_title', 'Сохранять состояние дерева'],
|
||||
['app_navigationTree_settings_state_description', 'Восстанавливает открытые узлы после обновления страницы'],
|
||||
['app_navigationTree_settings_folders_title', 'Папки'],
|
||||
['app_navigationTree_settings_folders_description', 'Позволяет открыть любой узел как начальный'],
|
||||
['app_navigationTree_node_delete_confirmation', 'Вы собираетесь удалить "{args.name}". Вы уверены?'],
|
||||
['app_navigationTree_node_delete_error', 'Ошибка удаления "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Ошибка переименования ноды'],
|
||||
|
||||
['app_navigationTree_node_empty', 'Пусто'],
|
||||
['app_navigationTree_node_not_found', 'Элемент не найден'],
|
||||
['app_navigationTree_connection_view', 'Отображение'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Упрощенное'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Продвинутое'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Настраиваемое'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Показывать системные объекты'],
|
||||
['app_navigationTree_refresh', 'Обновить'],
|
||||
['app_navigationTree_refresh_error', 'Не удалось обновить узел'],
|
||||
['app_navigationTree_node_change_name_error', 'Не удалось изменить название'],
|
||||
['app_navigationTree_context_disconnect', 'Отключить'],
|
||||
['app_navigationTree_connection_group_user', 'Пользовательские подключения'],
|
||||
['app_navigationTree_connection_group_shared', 'Общие подключения'],
|
||||
['app_shared_inlineEditor_dialog_title', 'Редактирование'],
|
||||
['app_shared_inlineEditor_dialog_apply', 'Применить'],
|
||||
['app_shared_inlineEditor_dialog_cancel', 'Отменить'],
|
||||
|
||||
@@ -10,35 +10,6 @@ export default [
|
||||
['app_shared_settingsMenu_theme', '主题'],
|
||||
['app_shared_settingsMenu_lang', '语言'],
|
||||
['app_topnavbar_connection_schema_manager_not_selected', '-- 为选择 --'],
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_openNodeTab', '打开'],
|
||||
['app_navigationTree_connection_view', '连接视图'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view_option_simple', '简单'],
|
||||
['app_navigationTree_connection_view_option_advanced', '完善'],
|
||||
['app_navigationTree_connection_view_option_custom', '自定义'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', '展示系统对象'],
|
||||
['app_navigationTree_refresh', '刷新'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_node_change_name_error', '更改名称时发生错误'],
|
||||
['app_navigationTree_context_disconnect', '断开连接'],
|
||||
['app_navigationTree_connection_group_user', '用户连接'],
|
||||
['app_navigationTree_connection_group_shared', '共享连接'],
|
||||
['app_shared_inlineEditor_dialog_title', '编辑模式'],
|
||||
['app_shared_inlineEditor_dialog_apply', '应用'],
|
||||
['app_shared_inlineEditor_dialog_cancel', '取消'],
|
||||
|
||||
@@ -119,7 +119,7 @@ UserInfoIncludes
|
||||
return authInfo as AuthInfo;
|
||||
}
|
||||
|
||||
finishFederatedAuthentication(authId: string, link?: boolean): ITask<UserInfo | null> {
|
||||
finishFederatedAuthentication(authId: string, linkUser?: boolean): ITask<UserInfo | null> {
|
||||
let activeTask: ITask<AuthInfo> | undefined;
|
||||
|
||||
return new AutoRunningTask<UserInfo | null>(() => this.performUpdate(
|
||||
@@ -139,7 +139,7 @@ UserInfoIncludes
|
||||
async () => {
|
||||
const { authInfo } = await this.graphQLService.sdk.getAuthStatus({
|
||||
authId,
|
||||
linkUser: link,
|
||||
linkUser,
|
||||
customIncludeOriginDetails: true,
|
||||
});
|
||||
return authInfo as AuthInfo;
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="preload" as="font"
|
||||
crossorigin="anonymous" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" crossorigin="anonymous" />
|
||||
<link rel="prefetch" href="icons/icons.svg" as="image" type="image/svg+xml" />
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -181,23 +181,33 @@ export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
const nodePaths = isResourceKeyList(key) ? key.list : [key];
|
||||
|
||||
await this.performUpdate(key, [], async () => {
|
||||
await this.graphQLService.sdk.navDeleteNodes({ nodePaths });
|
||||
|
||||
runInAction(() => {
|
||||
const parents: string[] = [];
|
||||
const deletedIds: string[][] = [];
|
||||
const deletedPaths: string[] = [];
|
||||
|
||||
try {
|
||||
for (const path of nodePaths) {
|
||||
const node = this.navNodeInfoResource.get(path);
|
||||
|
||||
if (node) {
|
||||
parents.push(node.parentId);
|
||||
deletedIds.push([path]);
|
||||
}
|
||||
await this.graphQLService.sdk.navDeleteNodes({ nodePaths: [path] });
|
||||
deletedPaths.push(path);
|
||||
}
|
||||
} finally {
|
||||
runInAction(() => {
|
||||
const deletionMap = new Map<string, string[]>();
|
||||
|
||||
this.deleteInNode(resourceKeyList(parents), deletedIds);
|
||||
});
|
||||
for (const path of deletedPaths) {
|
||||
const node = this.navNodeInfoResource.get(path);
|
||||
|
||||
if (node) {
|
||||
const deletedIds = deletionMap.get(node.parentId) ?? [];
|
||||
deletedIds.push(path);
|
||||
deletionMap.set(node.parentId, deletedIds);
|
||||
}
|
||||
}
|
||||
|
||||
const keys = resourceKeyList([...deletionMap.keys()]);
|
||||
const nodes = [...deletionMap.values()];
|
||||
|
||||
this.deleteInNode(keys, nodes);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -66,8 +66,8 @@ export class AuthenticationService extends Bootstrap {
|
||||
this.configureIdentityProvider = action;
|
||||
}
|
||||
|
||||
async authUser(providerId: string | null = null, link?: boolean): Promise<void> {
|
||||
await this.auth(false, { providerId, link });
|
||||
async authUser(providerId: string | null = null, linkUser?: boolean): Promise<void> {
|
||||
await this.auth(false, { providerId, linkUser });
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
@@ -170,7 +170,7 @@ export class AuthenticationService extends Bootstrap {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.auth(true, { accessRequest: true, providerId: null, link: true });
|
||||
await this.auth(true, { accessRequest: true, providerId: null, linkUser: false });
|
||||
}
|
||||
|
||||
register(): void {
|
||||
@@ -188,7 +188,7 @@ export class AuthenticationService extends Bootstrap {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.auth(false, { providerId: null, link: true, accessRequest: true });
|
||||
await this.auth(false, { providerId: null, linkUser: false, accessRequest: true });
|
||||
});
|
||||
this.authProviderService.requestAuthProvider.addHandler(this.requestAuthProviderHandler);
|
||||
}
|
||||
@@ -202,7 +202,7 @@ export class AuthenticationService extends Bootstrap {
|
||||
const action = contexts.getContext(sessionActionContext);
|
||||
|
||||
if (isAutoLoginSessionAction(data)) {
|
||||
const user = await this.userInfoResource.finishFederatedAuthentication(data['auth-id'], true);
|
||||
const user = await this.userInfoResource.finishFederatedAuthentication(data['auth-id'], false);
|
||||
|
||||
if (user && this.authPromise) {
|
||||
this.authDialogService.closeLoginForm(this.authPromise);
|
||||
|
||||
@@ -67,7 +67,7 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
|
||||
payload: {
|
||||
providerId,
|
||||
configurationId,
|
||||
link = false,
|
||||
linkUser = false,
|
||||
accessRequest = false,
|
||||
},
|
||||
options,
|
||||
@@ -111,7 +111,7 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
|
||||
}
|
||||
|
||||
async function login() {
|
||||
await dialogData.login(link);
|
||||
await dialogData.login(linkUser);
|
||||
rejectDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ interface IData {
|
||||
providers: AuthProvider[];
|
||||
configurations: AuthProvider[];
|
||||
|
||||
login: (link: boolean) => Promise<void>;
|
||||
login: (linkUser: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
@@ -139,7 +139,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async login(link: boolean): Promise<void> {
|
||||
async login(linkUser: boolean): Promise<void> {
|
||||
if (!state.activeProvider || this.authenticating) {
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
|
||||
try {
|
||||
await authInfoService.login(state.activeProvider.id, {
|
||||
credentials: state.credentials,
|
||||
linkUser: link,
|
||||
linkUser,
|
||||
});
|
||||
} catch (exception: any) {
|
||||
if (this.destroyed) {
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
export interface IAuthOptions {
|
||||
providerId: string | null;
|
||||
configurationId?: string;
|
||||
link?: boolean;
|
||||
linkUser?: boolean;
|
||||
accessRequest?: boolean;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export class PluginBootstrap extends Bootstrap {
|
||||
label: 'authentication_login',
|
||||
tooltip: 'authentication_login',
|
||||
},
|
||||
{ onSelect: () => this.authDialogService.showLoginForm(false, { providerId: null, link: true }) }
|
||||
{ onSelect: () => this.authDialogService.showLoginForm(false, { providerId: null, linkUser: false }) }
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -189,7 +189,8 @@ export class NavigationTabsService extends View<ITab> {
|
||||
}
|
||||
}
|
||||
|
||||
this.callHandlerCallback(tab, handler => handler.onClose);
|
||||
await this.callHandlerCallback(tab, handler => handler.onClose);
|
||||
await this.callHandlerCallback(tab, handler => handler.onUnload);
|
||||
}
|
||||
|
||||
this.closeTabSilent(tabId, skipHandlers);
|
||||
@@ -288,7 +289,7 @@ export class NavigationTabsService extends View<ITab> {
|
||||
}
|
||||
}
|
||||
|
||||
unloadTabs(): void {
|
||||
async unloadTabs(): Promise<void> {
|
||||
// if (this.administrationScreenService.publicDisabled) {
|
||||
// return;
|
||||
// }
|
||||
@@ -296,7 +297,7 @@ export class NavigationTabsService extends View<ITab> {
|
||||
for (const tab of this.tabsMap.values()) {
|
||||
if (tab.userId !== this.userInfoResource.getId()) {
|
||||
if (tab.restored) {
|
||||
this.callHandlerCallback(tab, handler => handler.onClose);
|
||||
await this.callHandlerCallback(tab, handler => handler.onUnload);
|
||||
tab.restored = false;
|
||||
}
|
||||
}
|
||||
@@ -313,10 +314,6 @@ export class NavigationTabsService extends View<ITab> {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const handler of this.handlers.values()) {
|
||||
await handler.onNavInit?.();
|
||||
}
|
||||
|
||||
const removedTabs: string[] = [];
|
||||
|
||||
for (const tabId of this.userTabsState.tabs) {
|
||||
@@ -347,7 +344,7 @@ export class NavigationTabsService extends View<ITab> {
|
||||
});
|
||||
}
|
||||
|
||||
private callHandlerCallback(tab: ITab, selector: (handler: TabHandler) => TabHandlerEvent | undefined) {
|
||||
private async callHandlerCallback(tab: ITab, selector: (handler: TabHandler) => TabHandlerEvent | undefined) {
|
||||
const handler = this.handlers.get(tab.handlerId);
|
||||
|
||||
if (!handler) {
|
||||
@@ -357,7 +354,7 @@ export class NavigationTabsService extends View<ITab> {
|
||||
const callback = selector(handler);
|
||||
|
||||
if (callback) {
|
||||
callback.call(handler, tab);
|
||||
await callback.call(handler, tab);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,9 +26,8 @@ export interface TabHandlerPanelProps<T = any> {
|
||||
}
|
||||
export type TabHandlerPanelComponent<T = any> = React.FunctionComponent<TabHandlerPanelProps<T>>;
|
||||
|
||||
export type TabHandlerNavInitEvent = () => Promise<void> | void;
|
||||
export type TabHandlerCloseEvent<T = any> = (tab: ITab<T>) => Promise<boolean> | boolean;
|
||||
export type TabHandlerEvent<T = any> = (tab: ITab<T>) => void;
|
||||
export type TabHandlerEvent<T = any> = (tab: ITab<T>) => Promise<void> | void;
|
||||
export type TabRestoreEvent<T = any> = (tab: ITab<T>) => Promise<boolean> | boolean;
|
||||
|
||||
export interface TabHandlerOptions<TState = any> {
|
||||
@@ -39,8 +38,7 @@ export interface TabHandlerOptions<TState = any> {
|
||||
canClose?: TabHandlerCloseEvent<TState>;
|
||||
onClose?: TabHandlerEvent<TState>;
|
||||
onRestore?: TabRestoreEvent<TState>;
|
||||
onNavInit?: TabHandlerNavInitEvent;
|
||||
onNavUnload?: TabHandlerNavInitEvent;
|
||||
onUnload?: TabHandlerEvent<TState>;
|
||||
extensions?: Array<IExtension<ITab<TState>>>;
|
||||
}
|
||||
|
||||
@@ -52,8 +50,7 @@ export class TabHandler<TState = any> {
|
||||
onClose?: TabHandlerEvent<TState>;
|
||||
canClose?: TabHandlerCloseEvent<TState>;
|
||||
onRestore?: TabRestoreEvent<TState>;
|
||||
onNavInit?: TabHandlerNavInitEvent;
|
||||
onNavUnload?: TabHandlerNavInitEvent;
|
||||
onUnload?: TabHandlerEvent<TState>;
|
||||
extensions?: Array<IExtension<ITab<TState>>>;
|
||||
|
||||
constructor(options: TabHandlerOptions<TState>) {
|
||||
@@ -64,8 +61,7 @@ export class TabHandler<TState = any> {
|
||||
this.canClose = options.canClose;
|
||||
this.onClose = options.onClose;
|
||||
this.onRestore = options.onRestore;
|
||||
this.onNavInit = options.onNavInit;
|
||||
this.onNavUnload = options.onNavUnload;
|
||||
this.onUnload = options.onUnload;
|
||||
this.extensions = options.extensions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
|
||||
@injectable()
|
||||
export class LocaleService extends Bootstrap {
|
||||
constructor(private readonly localizationService: LocalizationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.localizationService.addProvider(this.provider.bind(this));
|
||||
}
|
||||
|
||||
load(): void | Promise<void> { }
|
||||
|
||||
private async provider(locale: string) {
|
||||
switch (locale) {
|
||||
case 'ru':
|
||||
return (await import('./locales/ru')).default;
|
||||
case 'it':
|
||||
return (await import('./locales/it')).default;
|
||||
case 'zh':
|
||||
return (await import('./locales/zh')).default;
|
||||
default:
|
||||
return (await import('./locales/en')).default;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -10,9 +10,10 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo, useCallback, useEffect } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { EventTreeNodeClickFlag, EventTreeNodeExpandFlag, EventTreeNodeSelectFlag, FolderExplorer, FolderExplorerPath, Loader, useFolderExplorer, useMapResource, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { EventTreeNodeClickFlag, EventTreeNodeExpandFlag, EventTreeNodeSelectFlag, FolderExplorer, FolderExplorerPath, Loader, TreeNodeNested, TreeNodeNestedMessage, TREE_NODE_STYLES, useFolderExplorer, useMapResource, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { Translate } from '@cloudbeaver/core-localization';
|
||||
import { type NavNode, ROOT_NODE_PATH, NavTreeResource, NavNodeInfoResource, EObjectFeature } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
@@ -281,7 +282,7 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
const hasChildren = (children.data?.length || 0) > 0;
|
||||
const loaderAvailable = !foldersTree || context.folderExplorer.root === root;
|
||||
|
||||
return styled(useStyles(styles, style))(
|
||||
return styled(useStyles(TREE_NODE_STYLES, styles, style))(
|
||||
<>
|
||||
<ElementsTreeTools tree={tree} style={style} />
|
||||
<tree-box>
|
||||
@@ -305,11 +306,9 @@ export const ElementsTree = observer<Props>(function ElementsTree({
|
||||
bottom: dropOutside.bottom,
|
||||
})}
|
||||
>
|
||||
<NavigationNodeNested
|
||||
component={NavigationNodeElement}
|
||||
path={folderExplorer.state.path}
|
||||
root
|
||||
/>
|
||||
<TreeNodeNested root>
|
||||
<TreeNodeNestedMessage><Translate token='app_navigationTree_drop_here' /></TreeNodeNestedMessage>
|
||||
</TreeNodeNested>
|
||||
</drop-outside>
|
||||
<NavigationNodeNested
|
||||
ref={dropOutside.nestedRef}
|
||||
|
||||
@@ -12,6 +12,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { Translate } from '@cloudbeaver/core-localization';
|
||||
import { NavNodeInfoResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import { usePermission, EPermission } from '@cloudbeaver/core-root';
|
||||
import { CaptureView } from '@cloudbeaver/core-view';
|
||||
@@ -110,8 +111,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
emptyPlaceholder={() => styled(navigationTreeStyles)(
|
||||
<center>
|
||||
<message>
|
||||
No connections.<br />
|
||||
Use the top menu to setup connection to your database.
|
||||
<Translate token='app_navigationTree_empty_placeholder' />
|
||||
</message>
|
||||
</center>
|
||||
)}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export class NavNodeContextMenuService extends Bootstrap {
|
||||
.mapArray(data, nodeId => this.navNodeInfoResource.get(nodeId))
|
||||
.filter<NavNode>(Boolean as any)
|
||||
.map(node => node.name)
|
||||
.join();
|
||||
.join(', ');
|
||||
|
||||
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
|
||||
title: 'ui_data_delete_confirmation',
|
||||
|
||||
+10
@@ -8,8 +8,12 @@
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import { mockAuthentication } from '@cloudbeaver/core-authentication/mocks/mockAuthentication';
|
||||
import { createApp } from '@cloudbeaver/core-cli/tests/utils/createApp';
|
||||
import { getService } from '@cloudbeaver/core-cli/tests/utils/getService';
|
||||
import { createGQLEndpoint } from '@cloudbeaver/core-root/mocks/createGQLEndpoint';
|
||||
import { mockAppInit } from '@cloudbeaver/core-root/mocks/mockAppInit';
|
||||
import { mockGraphQL } from '@cloudbeaver/core-root/mocks/mockGraphQL';
|
||||
import datasourceContextSwitchPlugin from '@cloudbeaver/plugin-datasource-context-switch';
|
||||
import navigationTabsPlugin from '@cloudbeaver/plugin-navigation-tabs';
|
||||
import topAppBarPlugin from '@cloudbeaver/plugin-top-app-bar';
|
||||
@@ -17,6 +21,7 @@ import topAppBarPlugin from '@cloudbeaver/plugin-top-app-bar';
|
||||
import { navigationTreePlugin } from '../../manifest';
|
||||
import { NavNodeViewService } from './NavNodeViewService';
|
||||
|
||||
const endpoint = createGQLEndpoint();
|
||||
const app = createApp(
|
||||
topAppBarPlugin,
|
||||
navigationTabsPlugin,
|
||||
@@ -24,6 +29,11 @@ const app = createApp(
|
||||
navigationTreePlugin
|
||||
);
|
||||
|
||||
mockGraphQL(
|
||||
...mockAppInit(endpoint),
|
||||
...mockAuthentication(endpoint)
|
||||
);
|
||||
|
||||
beforeAll(() => app.init());
|
||||
|
||||
describe('filterDuplicates', () => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
export default [
|
||||
['app_navigationTree_openNodeTab', 'Open'],
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view', 'Connection view'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Simple'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Advanced'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Custom'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Show system objects'],
|
||||
['app_navigationTree_refresh', 'Refresh'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_node_change_name_error', 'Error occurred while changing name'],
|
||||
['app_navigationTree_context_disconnect', 'Disconnect'],
|
||||
['app_navigationTree_connection_group_user', 'User Connections'],
|
||||
['app_navigationTree_connection_group_shared', 'Shared Connections'],
|
||||
['app_navigationTree_empty_placeholder', 'No Connections.\nUse the top menu to setup connection to your database.'],
|
||||
['app_navigationTree_drop_here', 'Drop here...'],
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
export default [
|
||||
['app_navigationTree_openNodeTab', 'Apri'],
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view', 'Vista di connessione'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Semplice'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Avanzato'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Manuale'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Mostra oggetti di sistema'],
|
||||
['app_navigationTree_refresh', 'Aggiorna'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_context_disconnect', 'Scollega'],
|
||||
['app_navigationTree_connection_group_user', 'Connessioni Utente'],
|
||||
['app_navigationTree_empty_placeholder', 'Nessuna Connessione.\nUsa il menù in altro per impostare una nuova connessione.'],
|
||||
['app_navigationTree_drop_here', 'Drop here...'],
|
||||
];
|
||||
@@ -0,0 +1,36 @@
|
||||
export default [
|
||||
['app_navigationTree_openNodeTab', 'Открыть'],
|
||||
['app_navigationTree_search', 'Искать элементы...'],
|
||||
['app_navigationTree_limited', 'Отображено {args.limit} элементов'],
|
||||
|
||||
['app_navigationTree_action_link_with_editor', 'Синхронизовать с редактором'],
|
||||
['app_navigationTree_action_collapse_all', 'Свернуть все элементы'],
|
||||
|
||||
['app_navigationTree_settings_filter_title', 'Фильтр'],
|
||||
['app_navigationTree_settings_filter_description', 'Показывать фильтр'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Фильтровать скрытые элементы'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Показывает скрытые элементы при использовании фильтра'],
|
||||
['app_navigationTree_settings_state_title', 'Сохранять состояние дерева'],
|
||||
['app_navigationTree_settings_state_description', 'Восстанавливает открытые узлы после обновления страницы'],
|
||||
['app_navigationTree_settings_folders_title', 'Папки'],
|
||||
['app_navigationTree_settings_folders_description', 'Позволяет открыть любой узел как начальный'],
|
||||
['app_navigationTree_node_delete_confirmation', 'Вы собираетесь удалить "{args.name}". Вы уверены?'],
|
||||
['app_navigationTree_node_delete_error', 'Ошибка удаления "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Ошибка переименования ноды'],
|
||||
|
||||
['app_navigationTree_node_empty', 'Пусто'],
|
||||
['app_navigationTree_node_not_found', 'Элемент не найден'],
|
||||
['app_navigationTree_connection_view', 'Отображение'],
|
||||
['app_navigationTree_connection_view_option_simple', 'Упрощенное'],
|
||||
['app_navigationTree_connection_view_option_advanced', 'Продвинутое'],
|
||||
['app_navigationTree_connection_view_option_custom', 'Настраиваемое'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', 'Показывать системные объекты'],
|
||||
['app_navigationTree_refresh', 'Обновить'],
|
||||
['app_navigationTree_refresh_error', 'Не удалось обновить узел'],
|
||||
['app_navigationTree_node_change_name_error', 'Не удалось изменить название'],
|
||||
['app_navigationTree_context_disconnect', 'Отключить'],
|
||||
['app_navigationTree_connection_group_user', 'Пользовательские подключения'],
|
||||
['app_navigationTree_connection_group_shared', 'Общие подключения'],
|
||||
['app_navigationTree_empty_placeholder', 'Нет подключений.\nИспользуйте верхнее меню для создания подключения к базе.'],
|
||||
['app_navigationTree_drop_here', 'Drop here...'],
|
||||
];
|
||||
@@ -0,0 +1,33 @@
|
||||
export default [
|
||||
['app_navigationTree_search', 'Search elements...'],
|
||||
['app_navigationTree_limited', 'Elements are limited to {args.limit} items'],
|
||||
['app_navigationTree_action_link_with_editor', 'Link with editor'],
|
||||
['app_navigationTree_action_collapse_all', 'Collapse all'],
|
||||
['app_navigationTree_settings_filter_title', 'Filter'],
|
||||
['app_navigationTree_settings_filter_description', 'Show filtering field'],
|
||||
['app_navigationTree_settings_filter_all_title', 'Show collapsed'],
|
||||
['app_navigationTree_settings_filter_all_description', 'Shows filtered elements in collapsed folders'],
|
||||
['app_navigationTree_settings_state_title', 'Save tree state'],
|
||||
['app_navigationTree_settings_state_description', 'Reopen expanded nodes after page refresh'],
|
||||
['app_navigationTree_settings_folders_title', 'Folders'],
|
||||
['app_navigationTree_settings_folders_description', 'Open any node as tree root node'],
|
||||
['app_navigationTree_node_delete_confirmation', 'You\'re going to delete "{args.name}". Are you sure?'],
|
||||
['app_navigationTree_node_delete_error', 'Failed to delete "{args.name}'],
|
||||
['app_navigationTree_node_rename_error', 'Error occurred while renaming'],
|
||||
['app_navigationTree_node_empty', 'Empty'],
|
||||
['app_navigationTree_openNodeTab', '打开'],
|
||||
['app_navigationTree_connection_view', '连接视图'],
|
||||
['app_navigationTree_node_not_found', 'Node not found'],
|
||||
['app_navigationTree_connection_view_option_simple', '简单'],
|
||||
['app_navigationTree_connection_view_option_advanced', '完善'],
|
||||
['app_navigationTree_connection_view_option_custom', '自定义'],
|
||||
['app_navigationTree_connection_view_option_showSystemObjects', '展示系统对象'],
|
||||
['app_navigationTree_refresh', '刷新'],
|
||||
['app_navigationTree_refresh_error', 'Failed to refresh node'],
|
||||
['app_navigationTree_node_change_name_error', '更改名称时发生错误'],
|
||||
['app_navigationTree_context_disconnect', '断开连接'],
|
||||
['app_navigationTree_connection_group_user', '用户连接'],
|
||||
['app_navigationTree_connection_group_shared', '共享连接'],
|
||||
['app_navigationTree_empty_placeholder', 'No Connections.\nUse the top menu to setup connection to your database.'],
|
||||
['app_navigationTree_drop_here', 'Drop here...'],
|
||||
];
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { ElementsTreeToolsMenuService } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService';
|
||||
import { NavigationTreeBootstrap } from './NavigationTree/NavigationTreeBootstrap';
|
||||
import { NavigationTreeService } from './NavigationTree/NavigationTreeService';
|
||||
@@ -18,6 +19,7 @@ import { NavNodeViewService } from './NodesManager/NavNodeView/NavNodeViewServic
|
||||
export const navigationTreePlugin: PluginManifest = {
|
||||
info: { name: 'Navigation Tree plugin' },
|
||||
providers: [
|
||||
LocaleService,
|
||||
NavigationTreeService,
|
||||
ElementsTreeToolsMenuService,
|
||||
NavigationTreeBootstrap,
|
||||
|
||||
+1
-21
@@ -62,32 +62,12 @@ export class ObjectPropertyTableFooterService {
|
||||
onClick: async context => {
|
||||
const nodes = this.getSelectedNodes(context.data.tableState.selectedList)
|
||||
.filter(node => node.features?.includes(ENodeFeature.canDelete));
|
||||
const nodeNames = nodes.map(getNodeDisplayName);
|
||||
|
||||
const result = await this.commonDialogService.open(ConfirmationDialogDelete, {
|
||||
title: 'ui_data_delete_confirmation',
|
||||
message: `You're going to delete following items: "${nodeNames.join(', ')}". Are you sure?`,
|
||||
confirmActionText: 'ui_delete',
|
||||
});
|
||||
|
||||
if (result === DialogueStateResult.Rejected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deleted: string[] = [];
|
||||
|
||||
try {
|
||||
for (const node of nodes) {
|
||||
await this.navTreeResource.deleteNode(node.id);
|
||||
deleted.push(node.id);
|
||||
}
|
||||
await this.navTreeResource.deleteNode(resourceKeyList(nodes.map(node => node.id)));
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'Failed to delete item');
|
||||
}
|
||||
|
||||
if (deleted.length) {
|
||||
context.data.tableState.unselect(deleted);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+3
@@ -28,6 +28,9 @@ const menuStyles = css`
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
menu-container:not([|menuEmpty]) value {
|
||||
padding-right: 8px;
|
||||
}
|
||||
menu-box {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
@@ -183,6 +183,11 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
|
||||
try {
|
||||
this.exception = null;
|
||||
this.nodeInfo.nodeId = await this.actions.rename(this, this.nodeInfo.nodeId, name);
|
||||
|
||||
if (this.nodeInfo.parents.length > 0) {
|
||||
this.nodeInfo.parents.splice(this.nodeInfo.parents.length - 1, 1, this.nodeInfo.nodeId);
|
||||
}
|
||||
|
||||
this.markOutdated();
|
||||
this.saved = true;
|
||||
this.loaded = false;
|
||||
|
||||
+10
-5
@@ -65,13 +65,13 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
if (
|
||||
!['undefined', 'object'].includes(typeof value.nodeInfo)
|
||||
|| !['string', 'undefined'].includes(typeof value.name)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof value.nodeInfo?.nodeId)
|
||||
|| !['string', 'undefined'].includes(typeof value.nodeInfo?.nodeId)
|
||||
|| !['undefined', 'object'].includes(typeof value.nodeInfo?.parents)
|
||||
|| !['undefined', 'object'].includes(typeof value.executionContext)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof value.executionContext?.connectionId)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof value.executionContext?.id)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof value.executionContext?.defaultCatalog)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof value.executionContext?.defaultSchema)
|
||||
|| !['string', 'undefined'].includes(typeof value.executionContext?.connectionId)
|
||||
|| !['string', 'undefined'].includes(typeof value.executionContext?.id)
|
||||
|| !['string', 'undefined'].includes(typeof value.executionContext?.defaultCatalog)
|
||||
|| !['string', 'undefined'].includes(typeof value.executionContext?.defaultSchema)
|
||||
) {
|
||||
map.delete(key);
|
||||
}
|
||||
@@ -119,6 +119,11 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
||||
return dataSource;
|
||||
},
|
||||
onDestroy: (_, editorId) => this.deleteState(editorId),
|
||||
onUnload: async dataSource => {
|
||||
if (dataSource instanceof ResourceSqlDataSource) {
|
||||
await dataSource.write();
|
||||
}
|
||||
},
|
||||
canDestroy: async (dataSource, editorId) => {
|
||||
try {
|
||||
if (dataSource instanceof ResourceSqlDataSource) {
|
||||
|
||||
@@ -70,6 +70,7 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
getTabComponent: () => SqlEditorTab,
|
||||
getPanelComponent: () => SqlEditorPanel,
|
||||
onRestore: this.handleTabRestore.bind(this),
|
||||
onUnload: this.handleTabUnload.bind(this),
|
||||
onClose: this.handleTabClose.bind(this),
|
||||
canClose: this.handleCanTabClose.bind(this),
|
||||
extensions: [
|
||||
@@ -270,9 +271,9 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
typeof tab.handlerState.editorId !== 'string'
|
||||
|| typeof tab.handlerState.editorId !== 'string'
|
||||
|| typeof tab.handlerState.order !== 'number'
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentTabId)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.source)
|
||||
|| !['string', 'undefined', 'object'].includes(typeof tab.handlerState.currentModeId)
|
||||
|| !['string', 'undefined'].includes(typeof tab.handlerState.currentTabId)
|
||||
|| !['string', 'undefined'].includes(typeof tab.handlerState.source)
|
||||
|| !['string', 'undefined'].includes(typeof tab.handlerState.currentModeId)
|
||||
|| !Array.isArray(tab.handlerState.modeState)
|
||||
|| !Array.isArray(tab.handlerState.tabs)
|
||||
|| !Array.isArray(tab.handlerState.executionPlanTabs)
|
||||
@@ -448,17 +449,21 @@ export class SqlEditorTabService extends Bootstrap {
|
||||
return canDestroyDatasource;
|
||||
}
|
||||
|
||||
private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
|
||||
private async handleTabUnload(editorTab: ITab<ISqlEditorTabState>) {
|
||||
const dataSource = this.sqlDataSourceService.get(editorTab.handlerState.editorId);
|
||||
|
||||
if (dataSource?.executionContext) {
|
||||
await this.sqlEditorService.destroyContext(dataSource.executionContext);
|
||||
}
|
||||
|
||||
await this.sqlDataSourceService.destroy(editorTab.handlerState.editorId);
|
||||
await this.sqlDataSourceService.unload(editorTab.handlerState.editorId);
|
||||
|
||||
this.sqlResultTabsService.removeResultTabs(editorTab.handlerState);
|
||||
}
|
||||
|
||||
private async handleTabClose(editorTab: ITab<ISqlEditorTabState>) {
|
||||
await this.sqlDataSourceService.destroy(editorTab.handlerState.editorId);
|
||||
}
|
||||
}
|
||||
|
||||
function findMinimalFree(array: number[], base: number): number {
|
||||
|
||||
@@ -31,6 +31,7 @@ interface ISqlDataSourceFabric {
|
||||
key: string;
|
||||
getDataSource: ISqlDataSourceFactory;
|
||||
onDestroy?: (dataSource: ISqlDataSource, editorId: string) => Promise<void> | void;
|
||||
onUnload?: (dataSource: ISqlDataSource, editorId: string) => Promise<void> | void;
|
||||
canDestroy?: (dataSource: ISqlDataSource, editorId: string) => Promise<boolean> | boolean;
|
||||
}
|
||||
|
||||
@@ -146,6 +147,14 @@ export class SqlDataSourceService {
|
||||
if (activeProvider) {
|
||||
await this.destroyProvider(editorId, activeProvider);
|
||||
}
|
||||
}
|
||||
|
||||
async unload(editorId: string): Promise<void> {
|
||||
const activeProvider = this.providers.get(editorId);
|
||||
|
||||
if (activeProvider) {
|
||||
await this.unloadProvider(editorId, activeProvider);
|
||||
}
|
||||
|
||||
this.providers.delete(editorId);
|
||||
}
|
||||
@@ -158,8 +167,12 @@ export class SqlDataSourceService {
|
||||
this.dataSourceProviders.set(dataSourceOptions.key, dataSourceOptions);
|
||||
}
|
||||
|
||||
private async destroyProvider(editorId: string, provider: ISqlDataSourceProvider): Promise<void> {
|
||||
await provider.provider.onDestroy?.(provider.dataSource, editorId);
|
||||
private async unloadProvider(editorId: string, provider: ISqlDataSourceProvider): Promise<void> {
|
||||
await provider.provider.onUnload?.(provider.dataSource, editorId);
|
||||
await provider.dataSource.dispose();
|
||||
}
|
||||
|
||||
private async destroyProvider(editorId: string, provider: ISqlDataSourceProvider): Promise<void> {
|
||||
await provider.provider.onDestroy?.(provider.dataSource, editorId);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@cloudbeaver/product-default",
|
||||
"sideEffects": false,
|
||||
"version": "22.1.5",
|
||||
"version": "22.2.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
|
||||
Reference in New Issue
Block a user