From 4f0fa25d83eeb831eb02afca6c024dee17ccfc51 Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:10:53 +0100 Subject: [PATCH] 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 class WebAppUtils { 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 @@ public abstract class WebServiceServletBase extends HttpServlet { 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 @@ import java.net.URI; /** * 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 class WebServiceBindingFS extends WebServiceBindingBase { .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 class WebServiceBindingFS extends WebServiceBindingBase { ) ; } + + @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';