From e68c44ed737865373de7e61ce60546b2ed445840 Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Fri, 27 Oct 2023 11:32:36 +0200 Subject: [PATCH 01/10] CB-4019 update output log icon (#2090) Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../packages/plugin-sql-editor/public/icons/sql_output_logs.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg index bc9e4420c2..f188563b19 100644 --- a/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg +++ b/webapp/packages/plugin-sql-editor/public/icons/sql_output_logs.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 5255ee96db5012086a569574d923b15146f61250 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Fri, 27 Oct 2023 16:26:40 +0200 Subject: [PATCH 02/10] Remove redundant config --- .../workspace/conf/cloudbeaver.conf | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf b/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf index ba7e5de3b3..13b7b24448 100644 --- a/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf +++ b/server/test/io.cloudbeaver.test.platform/workspace/conf/cloudbeaver.conf @@ -60,27 +60,6 @@ "sqlite:sqlite_jdbc", "h2:h2_embedded", "h2:h2_embedded_v2" - ], - - plugins: { - aws: { - clouds: { - AWS: { - cloudName: "AWS", - - autoRegistrationEnabled: true, - autoRegistrationAccounts: [ ], - defaultRegions: [ ], - enabledServices: [ "rds", "redshift", "dynamodb", "documentdb" ], - - federatedAccessEnabled: true - } - } - }, - saml: { - signon-finish-uri = "/sso.html", - signout-finish-uri = "/sso.html" - } - } + ] } } From 584dd1ce4fde2736ac622fa30cfdd22a0e8ad711 Mon Sep 17 00:00:00 2001 From: Alexey Date: Fri, 27 Oct 2023 20:11:39 +0300 Subject: [PATCH 03/10] CB-4096 fix: synchronize user info data update (#2096) Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../packages/core-authentication/src/UsersResource.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index ba1d6e8f62..8d0692960e 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -182,7 +182,15 @@ export class UsersResource extends CachedMapResource { - await this.graphQLService.sdk.setUserAuthRole({ userId, authRole }); + await this.performUpdate(userId, undefined, async () => { + await this.graphQLService.sdk.setUserAuthRole({ userId, authRole }); + + const user = this.get(userId); + + if (user) { + user.authRole = authRole; + } + }); if (!skipUpdate) { this.markOutdated(userId); From a3bdfb68fe28d786642568689f8fbff2e36d0aad Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:59:05 +0100 Subject: [PATCH 04/10] CB-3778-bug-use-oracle-as-internal-db (#1920) * CB-3778 fetch limit fix for oracle and sql server * CB-3778 get offset limit part from sql dialect * CB-3778 offset limit order fix * CB-3778 schemas fix --------- Co-authored-by: Mikhailov Grigorii <49814763+ggxed@users.noreply.github.com> Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../db/cb_schema_create.sql | 2 +- .../db/cb_schema_update_10.sql | 2 +- .../db/cb_schema_update_13.sql | 2 +- .../db/cb_schema_update_2.sql | 2 +- .../db/cb_schema_update_5.sql | 2 +- .../db/cb_schema_update_7.sql | 4 ++-- .../db/cb_schema_update_9.sql | 2 +- .../security/CBEmbeddedSecurityController.java | 11 +++++++---- .../cloudbeaver/service/security/db/CBDatabase.java | 8 +++++++- 9 files changed, 22 insertions(+), 13 deletions(-) diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql index 2896c125c2..febb3482c3 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_create.sql @@ -308,7 +308,7 @@ CREATE TABLE {table_prefix}CB_USER_SECRETS SECRET_LABEL VARCHAR(128), SECRET_DESCRIPTION VARCHAR(1024), - ENCODING_TYPE VARCHAR(32) NOT NULL DEFAULT 'PLAINTEXT', + ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL, UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (USER_ID, SECRET_ID), diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql index 7fdc12e8d0..8d1d44cf82 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_10.sql @@ -7,7 +7,7 @@ CREATE TABLE {table_prefix}CB_USER_SECRETS SECRET_LABEL VARCHAR(128), SECRET_DESCRIPTION VARCHAR(1024), - UPDATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (USER_ID, SECRET_ID), FOREIGN KEY (USER_ID) REFERENCES {table_prefix}CB_USER (USER_ID) ON DELETE CASCADE diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_13.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_13.sql index 843ab47a31..200cc664ea 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_13.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_13.sql @@ -1,2 +1,2 @@ ALTER TABLE {table_prefix}CB_USER_SECRETS - ADD COLUMN ENCODING_TYPE VARCHAR(32) NOT NULL DEFAULT 'PLAINTEXT'; \ No newline at end of file + ADD COLUMN ENCODING_TYPE VARCHAR(32) DEFAULT 'PLAINTEXT' NOT NULL; \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_2.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_2.sql index ee14af9683..fbffddcab3 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_2.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_2.sql @@ -55,7 +55,7 @@ CREATE TABLE IF NOT EXISTS {table_prefix}CB_WORKSPACE( FOREIGN KEY(INSTANCE_ID) REFERENCES {table_prefix}CB_INSTANCE(INSTANCE_ID) ); -ALTER TABLE {table_prefix}CB_USER_CREDENTIALS ADD UPDATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; +ALTER TABLE {table_prefix}CB_USER_CREDENTIALS ADD UPDATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; ALTER TABLE {table_prefix}CB_SESSION ALTER COLUMN LAST_ACCESS_REMOTE_ADDRESS VARCHAR(128) NULL; ALTER TABLE {table_prefix}CB_SESSION ALTER COLUMN LAST_ACCESS_USER_AGENT VARCHAR(255) NULL; diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql index c15cbf971c..b7ccc68097 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_5.sql @@ -5,7 +5,7 @@ CREATE TABLE {table_prefix}CB_AUTH_TOKEN USER_ID VARCHAR(128), EXPIRATION_TIME TIMESTAMP NOT NULL, - CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (TOKEN_ID), FOREIGN KEY (SESSION_ID) REFERENCES {table_prefix}CB_SESSION (SESSION_ID) ON DELETE CASCADE, diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_7.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_7.sql index 410597bc0e..64cb9ca652 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_7.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_7.sql @@ -8,7 +8,7 @@ CREATE TABLE {table_prefix}CB_AUTH_ATTEMPT SESSION_TYPE VARCHAR(64) NOT NULL, APP_SESSION_STATE TEXT NOT NULL, - CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (AUTH_ID), FOREIGN KEY (SESSION_ID) REFERENCES {table_prefix}CB_SESSION (SESSION_ID) ON DELETE CASCADE @@ -21,7 +21,7 @@ CREATE TABLE {table_prefix}CB_AUTH_ATTEMPT_INFO AUTH_PROVIDER_CONFIGURATION_ID VARCHAR(128), AUTH_STATE TEXT NOT NULL, - CREATE_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CREATE_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, PRIMARY KEY (AUTH_ID, AUTH_PROVIDER_ID), FOREIGN KEY (AUTH_ID) REFERENCES {table_prefix}CB_AUTH_ATTEMPT (AUTH_ID) ON DELETE CASCADE diff --git a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_9.sql b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_9.sql index ca028aabbc..a6c7e3e712 100644 --- a/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_9.sql +++ b/server/bundles/io.cloudbeaver.service.security/db/cb_schema_update_9.sql @@ -2,4 +2,4 @@ ALTER TABLE {table_prefix}CB_AUTH_TOKEN ADD REFRESH_TOKEN_ID VARCHAR(128); ALTER TABLE {table_prefix}CB_AUTH_TOKEN - ADD REFRESH_TOKEN_EXPIRATION_TIME TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; \ No newline at end of file + ADD REFRESH_TOKEN_EXPIRATION_TIME TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL; \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 592892804f..4b68f9d8ae 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -375,10 +375,8 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter) // Read users try (PreparedStatement dbStat = dbCon.prepareStatement( database.normalizeTableNames("SELECT USER_ID,IS_ACTIVE,DEFAULT_AUTH_ROLE FROM {table_prefix}CB_USER" - + buildUsersFilter(filter) + "\nORDER BY USER_ID LIMIT ? OFFSET ?"))) { + + buildUsersFilter(filter) + "\nORDER BY USER_ID " + getOffsetLimitPart(filter)))) { int parameterIndex = setUsersFilterValues(dbStat, filter, 1); - dbStat.setInt(parameterIndex++, filter.getPage().getLimit()); - dbStat.setInt(parameterIndex++, filter.getPage().getOffset()); try (ResultSet dbResult = dbStat.executeQuery()) { while (dbResult.next()) { @@ -424,6 +422,10 @@ public SMUser[] findUsers(@NotNull SMUserFilter filter) } } + private String getOffsetLimitPart(@NotNull SMUserFilter filter) { + return database.getDialect().getOffsetLimitQueryPart(filter.getPage().getOffset(), filter.getPage().getLimit()); + } + private String buildUsersFilter(SMUserFilter filter) { StringBuilder where = new StringBuilder(); List whereParts = new ArrayList<>(); @@ -2758,9 +2760,10 @@ public void clearOldAuthAttemptInfo() throws DBException { "WHERE EXISTS " + "(SELECT 1 FROM {table_prefix}CB_AUTH_ATTEMPT AA " + "LEFT JOIN {table_prefix}CB_AUTH_TOKEN CAT ON AA.SESSION_ID = CAT.SESSION_ID " + - "WHERE (CAT.REFRESH_TOKEN_EXPIRATION_TIME < NOW() OR CAT.EXPIRATION_TIME IS NULL) " + + "WHERE (CAT.REFRESH_TOKEN_EXPIRATION_TIME < ? OR CAT.EXPIRATION_TIME IS NULL) " + "AND AA.AUTH_ID=AAI.AUTH_ID AND AUTH_STATUS='" + SMAuthStatus.EXPIRED + "') " + "AND CREATE_TIME Date: Mon, 30 Oct 2023 18:10:53 +0100 Subject: [PATCH 05/10] CB-4123 servlet for upload download fs files (#2078) * CB-4123 servlet for upload download fs files * CB-4123 use variables in fs servlet * CB-4123 use parameter nodepath in get * CB-4123 upload file fix * CB-4123 use project id and uri * CB-4124 add upload/download context actions * CB-4124 move fs extension to EE * CB-4124 export NavigationNodeControl styles * CB-4123 validate project permissions * CB-4123 code style fix * CB-4124 keep loader close to the title in notification * CB-4123 prettify error * CB-4113 resolve conflicts --------- Co-authored-by: naumov Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../src/io/cloudbeaver/utils/WebAppUtils.java | 11 ++ .../service/WebServiceServletBase.java | 14 +++ .../service/fs/WebServiceBindingFS.java | 37 ++++-- .../service/fs/model/WebFSServlet.java | 111 ++++++++++++++++++ .../SnackbarMarkups/SnackbarStatus.tsx | 2 +- .../packages/core-browser/src/selectFiles.ts | 7 +- .../packages/core-events/src/INotification.ts | 1 + .../src/ProcessNotificationController.ts | 6 +- .../core-localization/src/locales/en.ts | 2 + .../core-localization/src/locales/it.ts | 2 + .../core-localization/src/locales/ru.ts | 2 + .../core-localization/src/locales/zh.ts | 2 + .../uploadBlobResultSetExtension.ts | 4 +- .../src/Action/Actions/ACTION_DOWNLOAD.ts | 13 ++ .../src/Action/Actions/ACTION_UPLOAD.ts | 13 ++ webapp/packages/core-view/src/index.ts | 2 + .../plugin-navigation-tree/src/index.ts | 1 + 17 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java create mode 100644 webapp/packages/core-view/src/Action/Actions/ACTION_DOWNLOAD.ts create mode 100644 webapp/packages/core-view/src/Action/Actions/ACTION_UPLOAD.ts diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java index d076318f3e..b1cd5fa4fb 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebAppUtils.java @@ -16,9 +16,12 @@ */ package io.cloudbeaver.utils; +import io.cloudbeaver.DBWebException; +import io.cloudbeaver.WebProjectImpl; import io.cloudbeaver.auth.NoAuthCredentialsProvider; import io.cloudbeaver.model.app.WebApplication; import io.cloudbeaver.model.app.WebAuthApplication; +import io.cloudbeaver.model.session.WebSession; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; @@ -209,4 +212,12 @@ public static String getGlobalProjectId() { return RMProjectType.GLOBAL.getPrefix() + "_" + globalConfigurationName; } + public static WebProjectImpl getProjectById(WebSession webSession, String projectId) throws DBWebException { + WebProjectImpl project = webSession.getProjectById(projectId); + if (project == null) { + throw new DBWebException("Project '" + projectId + "' not found"); + } + return project; + } + } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java index 58f68f9f4a..280b01d21b 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceServletBase.java @@ -1,20 +1,31 @@ package io.cloudbeaver.service; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.data.json.JSONUtils; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; public abstract class WebServiceServletBase extends HttpServlet { private static final Log log = Log.getLog(WebServiceServletBase.class); + private static final Type MAP_STRING_OBJECT_TYPE = JSONUtils.MAP_TYPE_TOKEN; + private static final String REQUEST_PARAM_VARIABLES = "variables"; + private static final Gson gson = new GsonBuilder() + .serializeNulls() + .setPrettyPrinting() + .create(); private final CBApplication application; @@ -43,4 +54,7 @@ protected final void service(HttpServletRequest request, HttpServletResponse res protected abstract void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException; + protected Map getVariables(HttpServletRequest request) { + return gson.fromJson(request.getParameter(REQUEST_PARAM_VARIABLES), MAP_STRING_OBJECT_TYPE); + } } \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/WebServiceBindingFS.java b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/WebServiceBindingFS.java index e492bf0ced..854352edcb 100644 --- a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/WebServiceBindingFS.java +++ b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/WebServiceBindingFS.java @@ -17,9 +17,14 @@ package io.cloudbeaver.service.fs; import io.cloudbeaver.DBWebException; +import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.DBWBindingContext; +import io.cloudbeaver.service.DBWServiceBindingServlet; +import io.cloudbeaver.service.DBWServletContext; import io.cloudbeaver.service.WebServiceBindingBase; import io.cloudbeaver.service.fs.impl.WebServiceFS; +import io.cloudbeaver.service.fs.model.WebFSServlet; +import org.jkiss.dbeaver.DBException; import org.jkiss.utils.CommonUtils; import java.net.URI; @@ -27,7 +32,7 @@ /** * Web service implementation */ -public class WebServiceBindingFS extends WebServiceBindingBase { +public class WebServiceBindingFS extends WebServiceBindingBase implements DBWServiceBindingServlet { private static final String SCHEMA_FILE_NAME = "schema/service.fs.graphqls"; @@ -43,41 +48,48 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { .dataFetcher("fsFile", env -> getService(env).getFile(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("fileURI"))) + URI.create(env.getArgument("fileURI")) + ) ) .dataFetcher("fsListFiles", env -> getService(env).getFiles(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("folderURI"))) + URI.create(env.getArgument("folderURI")) + ) ) .dataFetcher("fsReadFileContentAsString", env -> getService(env).readFileContent(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("fileURI"))) + URI.create(env.getArgument("fileURI")) + ) ) ; model.getMutationType() .dataFetcher("fsCreateFile", env -> getService(env).createFile(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("fileURI"))) + URI.create(env.getArgument("fileURI")) + ) ) .dataFetcher("fsCreateFolder", env -> getService(env).createFolder(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("folderURI"))) + URI.create(env.getArgument("folderURI")) + ) ) .dataFetcher("fsDeleteFile", env -> getService(env).deleteFile(getWebSession(env), env.getArgument("projectId"), - URI.create(env.getArgument("fileURI"))) + URI.create(env.getArgument("fileURI")) + ) ) .dataFetcher("fsMoveFile", env -> getService(env).moveFile( getWebSession(env), env.getArgument("projectId"), URI.create(env.getArgument("fromURI")), - URI.create(env.getArgument("toURI"))) + URI.create(env.getArgument("toURI")) + ) ) .dataFetcher("fsWriteFileStringContent", env -> getService(env).writeFileContent( @@ -90,4 +102,13 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { ) ; } + + @Override + public void addServlets(CBApplication application, DBWServletContext servletContext) throws DBException { + servletContext.addServlet( + "fileSystems", + new WebFSServlet(application, getServiceImpl()), + application.getServicesURI() + "fs-data/*" + ); + } } diff --git a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java new file mode 100644 index 0000000000..3684946f3e --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/model/WebFSServlet.java @@ -0,0 +1,111 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 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.fs.model; + +import io.cloudbeaver.DBWebException; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.server.CBApplication; +import io.cloudbeaver.service.WebServiceServletBase; +import io.cloudbeaver.service.fs.DBWServiceFS; +import org.eclipse.jetty.server.Request; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.model.data.json.JSONUtils; +import org.jkiss.utils.CommonUtils; +import org.jkiss.utils.IOUtils; + +import javax.servlet.MultipartConfigElement; +import javax.servlet.annotation.MultipartConfig; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +@MultipartConfig() +public class WebFSServlet extends WebServiceServletBase { + private static final String PARAM_PROJECT_ID = "projectId"; + private final DBWServiceFS fs; + + public WebFSServlet(CBApplication application, DBWServiceFS fs) { + super(application); + this.fs = fs; + } + + @Override + protected void processServiceRequest(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + if (!session.isAuthorizedInSecurityManager()) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Anonymous access restricted."); + return; + } + if (request.getMethod().equals("POST")) { + doPost(session, request, response); + } else { + doGet(session, request, response); + } + + } + + private void doGet(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + String projectId = request.getParameter(PARAM_PROJECT_ID); + Path path = getPath(session, projectId, request.getParameter("fileURI")); + session.addInfoMessage("Download data ..."); + response.setHeader("Content-Type", "application/octet-stream"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + path.getFileName() + "\""); + response.setHeader("Content-Length", String.valueOf(Files.size(path))); + + try (InputStream is = Files.newInputStream(path)) { + IOUtils.copyStream(is, response.getOutputStream()); + } + } + + private void doPost(WebSession session, HttpServletRequest request, HttpServletResponse response) throws DBException, IOException { + // we need to set this attribute to get parts + request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement("")); + Map variables = getVariables(request); + String projectId = JSONUtils.getString(variables, PARAM_PROJECT_ID); + String uri = JSONUtils.getString(variables, "toURI"); + Path path = getPath(session, projectId, uri); + try { + for (Part part : request.getParts()) { + String fileName = part.getSubmittedFileName(); + if (CommonUtils.isEmpty(fileName)) { + continue; + } + try (InputStream is = part.getInputStream()) { + Files.copy(is, path.resolve(fileName)); + } + } + } catch (Exception e) { + throw new DBWebException("File Upload Failed: Unable to Save File to the File System", e); + } + } + + @NotNull + private Path getPath(WebSession session, String projectId, String uri) throws DBException { + if (CommonUtils.isEmpty(projectId)) { + throw new DBWebException("Project ID is not found"); + } + if (CommonUtils.isEmpty(uri)) { + throw new DBWebException("URI is not found"); + } + return session.getFileSystemManager(projectId).getPathFromString(session.getProgressMonitor(), uri); + } +} diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarStatus.tsx b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarStatus.tsx index a6893b2768..5a8280c6d2 100644 --- a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarStatus.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarStatus.tsx @@ -22,7 +22,7 @@ export const SnackbarStatus: React.FC = function SnackbarSt const styles = useS(style); return status === ENotificationType.Loading ? (
- +
) : ( diff --git a/webapp/packages/core-browser/src/selectFiles.ts b/webapp/packages/core-browser/src/selectFiles.ts index 3c15074e7e..9021a69b2b 100644 --- a/webapp/packages/core-browser/src/selectFiles.ts +++ b/webapp/packages/core-browser/src/selectFiles.ts @@ -6,10 +6,15 @@ * you may not use this file except in compliance with the License. */ -export function selectFiles(callback: (files: FileList | null) => any): void { +export function selectFiles(callback: (files: FileList | null) => any, multiple?: boolean): void { let removed = false; const input = document.createElement('input'); input.type = 'file'; + + if (multiple) { + input.multiple = true; + } + input.onchange = () => { callback(input.files); removed = true; diff --git a/webapp/packages/core-events/src/INotification.ts b/webapp/packages/core-events/src/INotification.ts index cee5197b84..0bbda71bb3 100644 --- a/webapp/packages/core-events/src/INotification.ts +++ b/webapp/packages/core-events/src/INotification.ts @@ -14,6 +14,7 @@ export interface IProcessNotificationState { init: (title: string, message?: string) => void; resolve: (title: string, message?: string) => void; reject: (error: Error, title?: string, message?: string) => void; + setMessage: (message: string | null) => void; } export enum ENotificationType { diff --git a/webapp/packages/core-events/src/ProcessNotificationController.ts b/webapp/packages/core-events/src/ProcessNotificationController.ts index f7052007de..3324bb4dfa 100644 --- a/webapp/packages/core-events/src/ProcessNotificationController.ts +++ b/webapp/packages/core-events/src/ProcessNotificationController.ts @@ -28,7 +28,7 @@ export class ProcessNotificationController implements IProcessNotificationState error: observable, title: observable, status: observable, - message: observable, + message: observable.ref, }); } @@ -52,4 +52,8 @@ export class ProcessNotificationController implements IProcessNotificationState this.message = message || errorDetails?.message || error.message; this.error = error; } + + setMessage(message: string | null) { + this.message = message; + } } diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 14498d138d..8984ff2217 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -92,6 +92,7 @@ export default [ ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], ['ui_download', 'Download'], + ['ui_download_file', 'Download file'], ['ui_upload', 'Upload'], ['ui_import', 'Import'], ['ui_view', 'View'], @@ -103,6 +104,7 @@ export default [ ['ui_upload_file', 'Upload file'], ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], + ['ui_upload_file_fail', 'Failed to upload file'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index 2c8a057faf..e960f7d9b9 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -76,6 +76,7 @@ export default [ ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], ['ui_download', 'Download'], + ['ui_download_file', 'Download file'], ['ui_upload', 'Upload'], ['ui_import', 'Import'], ['ui_view', 'View'], @@ -87,6 +88,7 @@ export default [ ['ui_upload_file', 'Upload file'], ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], + ['ui_upload_file_fail', 'Failed to upload file'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index a61d30fd4f..398af43e6b 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -88,6 +88,7 @@ export default [ ['ui_close_all_to_the_left', 'Закрыть все слева'], ['ui_or', 'Или'], ['ui_download', 'Cкачать'], + ['ui_download_file', 'Скачать файл'], ['ui_upload', 'Загрузить'], ['ui_import', 'Импортировать'], ['ui_view', 'Смотреть'], @@ -99,6 +100,7 @@ export default [ ['ui_upload_file', 'Загрузить файл'], ['ui_upload_files', 'Загрузить файлы'], ['ui_upload_files_duplicate_error', 'Файлы с такими именами уже существуют'], + ['ui_upload_file_fail', 'Не удалось загрузить файл'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index dcd8b9e5a2..45b9f506ce 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -89,6 +89,7 @@ export default [ ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], ['ui_download', 'Download'], + ['ui_download_file', 'Download file'], ['ui_upload', 'Upload'], ['ui_import', 'Import'], ['ui_view', 'View'], @@ -100,6 +101,7 @@ export default [ ['ui_upload_file', 'Upload file'], ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], + ['ui_upload_file_fail', 'Failed to upload file'], ['root_permission_denied', '您没有权限'], ['root_permission_no_permission', '您没有权限执行此操作'], diff --git a/webapp/packages/core-sdk/src/Extensions/uploadBlobResultSetExtension.ts b/webapp/packages/core-sdk/src/Extensions/uploadBlobResultSetExtension.ts index c80271ed8c..10b0a7a1fe 100644 --- a/webapp/packages/core-sdk/src/Extensions/uploadBlobResultSetExtension.ts +++ b/webapp/packages/core-sdk/src/Extensions/uploadBlobResultSetExtension.ts @@ -9,11 +9,11 @@ import { GlobalConstants } from '@cloudbeaver/core-utils'; import type { CustomGraphQLClient, UploadProgressEvent } from '../CustomGraphQLClient'; -export interface IUploadDriverLibraryExtension { +export interface IUploadBlobResultSetExtension { uploadBlobResultSet: (fileId: string, data: Blob, onUploadProgress?: (event: UploadProgressEvent) => void) => Promise; } -export function uploadBlobResultSetExtension(client: CustomGraphQLClient): IUploadDriverLibraryExtension { +export function uploadBlobResultSetExtension(client: CustomGraphQLClient): IUploadBlobResultSetExtension { return { uploadBlobResultSet(fileId: string, data: Blob, onUploadProgress?: (event: UploadProgressEvent) => void): Promise { // api/resultset/blob diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_DOWNLOAD.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_DOWNLOAD.ts new file mode 100644 index 0000000000..931afffec5 --- /dev/null +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_DOWNLOAD.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 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 { createAction } from '../createAction'; + +export const ACTION_DOWNLOAD = createAction('download', { + label: 'ui_download', + icon: '/icons/export.svg', +}); diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_UPLOAD.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_UPLOAD.ts new file mode 100644 index 0000000000..437a5671a8 --- /dev/null +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_UPLOAD.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 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 { createAction } from '../createAction'; + +export const ACTION_UPLOAD = createAction('upload', { + label: 'ui_upload', + icon: '/icons/import.svg', +}); diff --git a/webapp/packages/core-view/src/index.ts b/webapp/packages/core-view/src/index.ts index 197185f995..ce0e214a88 100644 --- a/webapp/packages/core-view/src/index.ts +++ b/webapp/packages/core-view/src/index.ts @@ -16,6 +16,8 @@ export * from './Action/Actions/ACTION_SETTINGS'; export * from './Action/Actions/ACTION_UNDO'; export * from './Action/Actions/ACTION_ZOOM_IN'; export * from './Action/Actions/ACTION_ZOOM_OUT'; +export * from './Action/Actions/ACTION_DOWNLOAD'; +export * from './Action/Actions/ACTION_UPLOAD'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_REDO'; export * from './Action/KeyBinding/Bindings/KEY_BINDING_UNDO'; diff --git a/webapp/packages/plugin-navigation-tree/src/index.ts b/webapp/packages/plugin-navigation-tree/src/index.ts index 762055ddd2..9f2df7770f 100644 --- a/webapp/packages/plugin-navigation-tree/src/index.ts +++ b/webapp/packages/plugin-navigation-tree/src/index.ts @@ -32,6 +32,7 @@ export { default as ElementsTreeToolsStyles } from './NavigationTree/ElementsTre export { default as ElementsTreeFilterStyles } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeFilter.m.css'; export { default as NavigationNodeNestedStyles } from './NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.m.css'; export { default as NavigationNodeControlRendererStyles } from './NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.m.css'; +export { default as NavigationNodeControlStyles } from './NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.m.css'; export * from './NavigationTree/NavigationTreeLoader'; From b2eab058c9c41f03e71882b6fe20e3dda5ef7cb0 Mon Sep 17 00:00:00 2001 From: Alexey Date: Tue, 31 Oct 2023 15:57:27 +0300 Subject: [PATCH 06/10] CB-3846 feat: allow an administrator to use data export (#2095) Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../plugin-data-export/src/DataExportMenuService.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts index 4dcec1dcf6..e3d1b2c220 100644 --- a/webapp/packages/plugin-data-export/src/DataExportMenuService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportMenuService.ts @@ -5,11 +5,12 @@ * Licensed under the Apache License, Version 2.0. * you may not use this file except in compliance with the License. */ +import { EAdminPermission } from '@cloudbeaver/core-authentication'; import { createConnectionParam, DATA_CONTEXT_CONNECTION } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, IMenuContext } from '@cloudbeaver/core-dialogs'; -import { LocalizationService } from '@cloudbeaver/core-localization'; import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigation-tree'; +import { SessionPermissionsResource } from '@cloudbeaver/core-root'; import { ACTION_EXPORT, ActionService, DATA_CONTEXT_MENU_NESTED, MenuService } from '@cloudbeaver/core-view'; import { IDatabaseDataSource, IDataContainerOptions, ITableFooterMenuContext, TableFooterMenuService } from '@cloudbeaver/plugin-data-viewer'; import type { IDataQueryOptions } from '@cloudbeaver/plugin-sql-editor'; @@ -25,7 +26,7 @@ export class DataExportMenuService { private readonly dataExportSettingsService: DataExportSettingsService, private readonly actionService: ActionService, private readonly menuService: MenuService, - private readonly localizationService: LocalizationService, + private readonly sessionPermissionsResource: SessionPermissionsResource, ) {} register(): void { @@ -106,9 +107,14 @@ export class DataExportMenuService { } private isDisabled() { + if (this.sessionPermissionsResource.has(EAdminPermission.admin)) { + return false; + } + if (this.dataExportSettingsService.settings.isValueDefault('disabled')) { return this.dataExportSettingsService.deprecatedSettings.getValue('disabled'); } + return this.dataExportSettingsService.settings.getValue('disabled'); } } From 0f9e7b650cc6c2712bfeaf09dbefd882bb8b6111 Mon Sep 17 00:00:00 2001 From: alex <48489896+devnaumov@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:16:09 +0100 Subject: [PATCH 07/10] CB-4005 add wrap mode (#2081) * CB-4005 add wrap mode * CB-4005 save wrap state in user settings * CB-4005 add tooltips * CB-4005 review fixes --------- Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../core-localization/src/locales/en.ts | 1 + .../core-localization/src/locales/it.ts | 1 + .../core-localization/src/locales/ru.ts | 1 + .../core-localization/src/locales/zh.ts | 1 + .../src/ContextMenu/MenuBar/MenuBarItem.tsx | 22 ++++----- .../OutputLogs/ACTION_SHOW_OUTPUT_LOGS.ts | 7 +++ .../OutputLogs/IOutputLogTypes.ts | 7 +++ .../OutputLogs/OUTPUT_LOGS_FILTER_MENU.ts | 2 +- .../OutputLogs/OUTPUT_LOGS_MENU.ts | 10 ++++ .../OutputLogs/OUTPUT_LOGS_SETTINGS_MENU.ts | 10 ++++ .../OutputLogs/OutputLogTypesFilterMenu.m.css | 14 ------ .../OutputLogs/OutputLogsMenu.m.css | 3 ++ ...TypesFilterMenu.tsx => OutputLogsMenu.tsx} | 21 ++++---- .../OutputLogs/OutputLogsPanel.tsx | 20 +++++--- .../OutputLogs/OutputLogsService.ts | 23 ++++++++- .../OutputLogs/OutputLogsToolbar.tsx | 6 +-- .../OutputLogs/OutputMenuBootstrap.ts | 48 +++++++++++++++++-- .../plugin-sql-editor/src/locales/en.ts | 1 + .../plugin-sql-editor/src/locales/it.ts | 1 + .../plugin-sql-editor/src/locales/ru.ts | 1 + .../plugin-sql-editor/src/locales/zh.ts | 1 + 21 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_MENU.ts create mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OUTPUT_LOGS_SETTINGS_MENU.ts delete mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogTypesFilterMenu.m.css create mode 100644 webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/OutputLogsMenu.m.css rename webapp/packages/plugin-sql-editor/src/SqlResultTabs/OutputLogs/{OutputLogTypesFilterMenu.tsx => OutputLogsMenu.tsx} (54%) diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 8984ff2217..7332c011f9 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -105,6 +105,7 @@ export default [ ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], ['ui_upload_file_fail', 'Failed to upload file'], + ['ui_filter', 'Filter'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index e960f7d9b9..c310a4e35d 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -89,6 +89,7 @@ export default [ ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], ['ui_upload_file_fail', 'Failed to upload file'], + ['ui_filter', 'Filter'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 398af43e6b..f7030e15e3 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -101,6 +101,7 @@ export default [ ['ui_upload_files', 'Загрузить файлы'], ['ui_upload_files_duplicate_error', 'Файлы с такими именами уже существуют'], ['ui_upload_file_fail', 'Не удалось загрузить файл'], + ['ui_filter', 'Фильтр'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 45b9f506ce..7c0a1619fa 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -102,6 +102,7 @@ export default [ ['ui_upload_files', 'Upload files'], ['ui_upload_files_duplicate_error', 'Files with the same name already exist'], ['ui_upload_file_fail', 'Failed to upload file'], + ['ui_filter', 'Filter'], ['root_permission_denied', '您没有权限'], ['root_permission_no_permission', '您没有权限执行此操作'], diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx index f3b2b7c472..fc869bb06e 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx @@ -35,29 +35,23 @@ export const MenuBarItem = observer( const title = translate(rest.title); return (