diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
index 810c538edd..9f16c555c9 100644
--- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
+++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF
@@ -27,6 +27,7 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.websocket,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
+ io.cloudbeaver.model.fs,
io.cloudbeaver.model.rm,
io.cloudbeaver.model.rm.local,
io.cloudbeaver.model.rm.lock,
diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/fs/FSUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/fs/FSUtils.java
new file mode 100644
index 0000000000..9a8a4f240d
--- /dev/null
+++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/fs/FSUtils.java
@@ -0,0 +1,27 @@
+/*
+ * 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.model.fs;
+
+import org.jkiss.code.NotNull;
+import org.jkiss.dbeaver.model.fs.DBFVirtualFileSystem;
+
+public class FSUtils {
+ @NotNull
+ public static String makeUniqueFsId(@NotNull DBFVirtualFileSystem fileSystem) {
+ return fileSystem.getType() + ":" + fileSystem.getId();
+ }
+}
diff --git a/server/bundles/io.cloudbeaver.server/schema/service.navigator.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.navigator.graphqls
index d38686e06e..4bc24544d4 100644
--- a/server/bundles/io.cloudbeaver.server/schema/service.navigator.graphqls
+++ b/server/bundles/io.cloudbeaver.server/schema/service.navigator.graphqls
@@ -78,7 +78,7 @@ type NavigatorNodeInfo {
# Associated object. Return value depends on the node type - connectionId for connection node, resource path for resource node, etc.
# null - if node currently not support this property
- objectId: String
+ objectId: String @since(version: "23.2.4")
# Supported features: item, container, leaf
# canDelete, canRename
diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java
index 3aa7f4e777..b53a5c39c8 100644
--- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java
+++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebNavigatorNodeInfo.java
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebProjectImpl;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.model.WebPropertyInfo;
+import io.cloudbeaver.model.fs.FSUtils;
import io.cloudbeaver.model.rm.DBNResourceManagerProject;
import io.cloudbeaver.model.rm.DBNResourceManagerResource;
import io.cloudbeaver.model.session.WebSession;
@@ -32,6 +33,7 @@ import org.jkiss.dbeaver.model.edit.DBEObjectRenamer;
import org.jkiss.dbeaver.model.meta.Association;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.*;
+import org.jkiss.dbeaver.model.navigator.fs.DBNFileSystem;
import org.jkiss.dbeaver.model.navigator.fs.DBNPathBase;
import org.jkiss.dbeaver.model.rm.RMProject;
import org.jkiss.dbeaver.model.rm.RMProjectPermission;
@@ -283,6 +285,8 @@ public class WebNavigatorNodeInfo {
public String getObjectId() {
if (node instanceof DBNPathBase dbnPath) {
return dbnPath.getPath().toUri().toString();
+ } else if (node instanceof DBNFileSystem dbnFs) {
+ return FSUtils.makeUniqueFsId(dbnFs.getFileSystem());
}
return null;
}
diff --git a/server/bundles/io.cloudbeaver.service.fs/plugin.xml b/server/bundles/io.cloudbeaver.service.fs/plugin.xml
index 09ad0fdeca..e3cbaf3f82 100644
--- a/server/bundles/io.cloudbeaver.service.fs/plugin.xml
+++ b/server/bundles/io.cloudbeaver.service.fs/plugin.xml
@@ -8,7 +8,4 @@
class="io.cloudbeaver.service.fs.WebServiceBindingFS">
-
-
-
diff --git a/server/bundles/io.cloudbeaver.service.fs/schema/service.fs.graphqls b/server/bundles/io.cloudbeaver.service.fs/schema/service.fs.graphqls
index 558d088c43..50ce1ef548 100644
--- a/server/bundles/io.cloudbeaver.service.fs/schema/service.fs.graphqls
+++ b/server/bundles/io.cloudbeaver.service.fs/schema/service.fs.graphqls
@@ -5,8 +5,15 @@ type FSFile @since(version: "23.2.2") {
metaData: Object!
}
+type FSFileSystem @since(version: "23.2.4") {
+ id: ID!
+ requiredAuth: String
+}
+
extend type Query @since(version: "23.2.2") {
- fsListFileSystems(projectId: ID!): [String!]!
+ fsListFileSystems(projectId: ID!): [FSFileSystem!]!
+
+ fsFileSystem(projectId: ID!, fileSystemId: ID!): FSFileSystem! @since(version: "23.2.4")
fsFile(projectId: ID!, fileURI: String!): FSFile!
@@ -21,14 +28,14 @@ extend type Mutation @since(version: "23.2.2") {
fsCreateFolder(projectId: ID!, folderURI:String!): FSFile!
- fsDeleteFile(projectId: ID!, fileURI:String!): Boolean!
+ fsDelete(projectId: ID!, fileURI:String!): Boolean!
- fsMoveFile(projectId: ID!, fromURI: String!, toURI: String!): FSFile!
+ fsMove(projectId: ID!, fromURI: String!, toURI: String!): FSFile!
fsWriteFileStringContent(
projectId: ID!,
fileURI:String!,
data: String!,
forceOverwrite: Boolean!
- ): Boolean!
+ ): FSFile!
}
\ No newline at end of file
diff --git a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/DBWServiceFS.java b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/DBWServiceFS.java
index 461c088b0e..e4d37b4072 100644
--- a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/DBWServiceFS.java
+++ b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/DBWServiceFS.java
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
import io.cloudbeaver.service.fs.model.FSFile;
+import io.cloudbeaver.service.fs.model.FSFileSystem;
import org.jkiss.code.NotNull;
import java.net.URI;
@@ -29,9 +30,17 @@ import java.net.URI;
*/
public interface DBWServiceFS extends DBWService {
@NotNull
- String[] getAvailableFileSystems(@NotNull WebSession webSession, @NotNull String projectId)
+ FSFileSystem[] getAvailableFileSystems(@NotNull WebSession webSession, @NotNull String projectId)
throws DBWebException;
+
+ @NotNull
+ FSFileSystem getFileSystem(
+ @NotNull WebSession webSession,
+ @NotNull String projectId,
+ @NotNull String fileSystemId
+ ) throws DBWebException;
+
@NotNull
FSFile getFile(
@NotNull WebSession webSession,
@@ -53,7 +62,7 @@ public interface DBWServiceFS extends DBWService {
@NotNull URI fileURI
) throws DBWebException;
- boolean writeFileContent(
+ FSFile writeFileContent(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull URI fileURI,
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 854352edcb..4f7e5e6552 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
@@ -45,6 +45,13 @@ public class WebServiceBindingFS extends WebServiceBindingBase imp
model.getQueryType()
.dataFetcher("fsListFileSystems",
env -> getService(env).getAvailableFileSystems(getWebSession(env), env.getArgument("projectId")))
+ .dataFetcher("fsFileSystem",
+ env -> getService(env).getFileSystem(
+ getWebSession(env),
+ env.getArgument("projectId"),
+ env.getArgument("fileSystemId")
+ )
+ )
.dataFetcher("fsFile",
env -> getService(env).getFile(getWebSession(env),
env.getArgument("projectId"),
@@ -77,13 +84,13 @@ public class WebServiceBindingFS extends WebServiceBindingBase imp
URI.create(env.getArgument("folderURI"))
)
)
- .dataFetcher("fsDeleteFile",
+ .dataFetcher("fsDelete",
env -> getService(env).deleteFile(getWebSession(env),
env.getArgument("projectId"),
URI.create(env.getArgument("fileURI"))
)
)
- .dataFetcher("fsMoveFile",
+ .dataFetcher("fsMove",
env -> getService(env).moveFile(
getWebSession(env),
env.getArgument("projectId"),
diff --git a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/impl/WebServiceFS.java b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/impl/WebServiceFS.java
index 2c0a7596d5..e527149b2f 100644
--- a/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/impl/WebServiceFS.java
+++ b/server/bundles/io.cloudbeaver.service.fs/src/io/cloudbeaver/service/fs/impl/WebServiceFS.java
@@ -17,14 +17,17 @@
package io.cloudbeaver.service.fs.impl;
import io.cloudbeaver.DBWebException;
+import io.cloudbeaver.model.fs.FSUtils;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.fs.DBWServiceFS;
import io.cloudbeaver.service.fs.model.FSFile;
+import io.cloudbeaver.service.fs.model.FSFileSystem;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
-import org.jkiss.dbeaver.model.fs.DBFVirtualFileSystem;
+import org.jkiss.dbeaver.registry.fs.FileSystemProviderRegistry;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -35,19 +38,47 @@ public class WebServiceFS implements DBWServiceFS {
@NotNull
@Override
- public String[] getAvailableFileSystems(@NotNull WebSession webSession, @NotNull String projectId)
+ public FSFileSystem[] getAvailableFileSystems(@NotNull WebSession webSession, @NotNull String projectId)
throws DBWebException {
try {
+ var fsRegistry = FileSystemProviderRegistry.getInstance();
return webSession.getFileSystemManager(projectId)
.getVirtualFileSystems()
.stream()
- .map(DBFVirtualFileSystem::getType)
- .toArray(String[]::new);
+ .map(fs -> new FSFileSystem(
+ FSUtils.makeUniqueFsId(fs),
+ fsRegistry.getProvider(fs.getProviderId()).getRequiredAuth()
+ )
+ )
+ .toArray(FSFileSystem[]::new);
} catch (Exception e) {
throw new DBWebException("Failed to load file systems: " + e.getMessage(), e);
}
}
+ @NotNull
+ @Override
+ public FSFileSystem getFileSystem(
+ @NotNull WebSession webSession,
+ @NotNull String projectId,
+ @NotNull String fileSystemId
+ ) throws DBWebException {
+ try {
+ var fsRegistry = FileSystemProviderRegistry.getInstance();
+ return webSession.getFileSystemManager(projectId)
+ .getVirtualFileSystems()
+ .stream()
+ .filter(fs -> FSUtils.makeUniqueFsId(fs).equals(fileSystemId))
+ .findFirst()
+ .map(fs -> new FSFileSystem(
+ FSUtils.makeUniqueFsId(fs),
+ fsRegistry.getProvider(fs.getProviderId()).getRequiredAuth()
+ )).orElseThrow(() -> new DBWebException("File system not found"));
+ } catch (Exception e) {
+ throw new DBWebException("Failed to get file system: " + e.getMessage(), e);
+ }
+ }
+
@NotNull
@Override
public FSFile getFile(@NotNull WebSession webSession, @NotNull String projectId, @NotNull URI fileUri)
@@ -80,15 +111,17 @@ public class WebServiceFS implements DBWServiceFS {
public String readFileContent(@NotNull WebSession webSession, @NotNull String projectId, @NotNull URI fileUri)
throws DBWebException {
try {
- Path filePath = webSession.getFileSystemManager(projectId).getPathFromURI(webSession.getProgressMonitor(), fileUri);
- return Files.readString(filePath);
+ Path filePath = webSession.getFileSystemManager(projectId)
+ .getPathFromURI(webSession.getProgressMonitor(), fileUri);
+ var data = Files.readAllBytes(filePath);
+ return new String(data, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new DBWebException("Failed to read file content: " + e.getMessage(), e);
}
}
@Override
- public boolean writeFileContent(
+ public FSFile writeFileContent(
@NotNull WebSession webSession,
@NotNull String projectId,
@NotNull URI fileURI,
@@ -97,12 +130,13 @@ public class WebServiceFS implements DBWServiceFS {
)
throws DBWebException {
try {
- Path filePath = webSession.getFileSystemManager(projectId).getPathFromURI(webSession.getProgressMonitor(), fileURI);
+ Path filePath = webSession.getFileSystemManager(projectId)
+ .getPathFromURI(webSession.getProgressMonitor(), fileURI);
if (!forceOverwrite && Files.exists(filePath)) {
throw new DBException("Cannot overwrite exist file");
}
Files.writeString(filePath, data);
- return true;
+ return new FSFile(filePath);
} catch (Exception e) {
throw new DBWebException("Failed to write file content: " + e.getMessage(), e);
}
diff --git a/webapp/packages/core-blocks/src/Containers/ColoredContainer.m.css b/webapp/packages/core-blocks/src/Containers/ColoredContainer.m.css
index 2d38b9b425..4c30c0cb88 100644
--- a/webapp/packages/core-blocks/src/Containers/ColoredContainer.m.css
+++ b/webapp/packages/core-blocks/src/Containers/ColoredContainer.m.css
@@ -1,3 +1,6 @@
-.coloredContainer {
+.secondary {
composes: theme-background-secondary theme-text-on-secondary from global;
}
+.surface {
+ composes: theme-background-surface theme-text-on-surface from global;
+}
diff --git a/webapp/packages/core-blocks/src/Containers/ColoredContainer.tsx b/webapp/packages/core-blocks/src/Containers/ColoredContainer.tsx
index fe7cce0814..ec572b0904 100644
--- a/webapp/packages/core-blocks/src/Containers/ColoredContainer.tsx
+++ b/webapp/packages/core-blocks/src/Containers/ColoredContainer.tsx
@@ -15,13 +15,14 @@ import { filterContainerFakeProps, getContainerProps } from './filterContainerFa
import type { IContainerProps } from './IContainerProps';
import elementsSizeStyles from './shared/ElementsSize.m.css';
-export const ColoredContainer = forwardRef>(function ColoredContainer(
- { className, ...rest },
- ref,
-) {
+interface Props extends IContainerProps, React.HTMLAttributes {
+ surface?: boolean;
+}
+
+export const ColoredContainer = forwardRef(function ColoredContainer({ className, surface, ...rest }, ref) {
const styles = useS(coloredContainerStyles, containerStyles, elementsSizeStyles);
const divProps = filterContainerFakeProps(rest);
const containerProps = getContainerProps(rest);
- return ;
+ return ;
});
diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts
index daf26f1667..fa14691ce9 100644
--- a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts
+++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts
@@ -322,13 +322,13 @@ export class NavNodeManagerService extends Bootstrap {
let icon: string | undefined;
let canOpen = false;
- if (NodeManagerUtils.isDatabaseObject(nodeId)) {
- const node = this.getNode(nodeId);
-
- if (node) {
- name = node.name;
- icon = node.icon;
+ const node = this.getNode(nodeId);
+ if (node) {
+ name = node.name;
+ icon = node.icon;
+ projectId ||= node.projectId;
+ if (NodeManagerUtils.isDatabaseObject(nodeId)) {
if (node.folder) {
const parent = this.getParent(node);
folderId = nodeId;
diff --git a/webapp/packages/core-projects/src/isResourceOfType.ts b/webapp/packages/core-projects/src/isResourceOfType.ts
index 2e9f076aba..3f8a9bbfc2 100644
--- a/webapp/packages/core-projects/src/isResourceOfType.ts
+++ b/webapp/packages/core-projects/src/isResourceOfType.ts
@@ -8,5 +8,5 @@
import type { ProjectInfoResourceType } from './ProjectInfoResource';
export function isResourceOfType(resourceType: ProjectInfoResourceType, name: string): boolean {
- return resourceType.fileExtensions.some(type => name.endsWith(`.${type}`));
+ return resourceType.fileExtensions.some(type => name.toLowerCase().endsWith(`.${type.toLowerCase()}`));
}
diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts
index 20ab3ebac6..c89974080a 100644
--- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts
+++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts
@@ -214,8 +214,6 @@ export class AuthenticationService extends Bootstrap {
this.authProviderService.requestAuthProvider.addHandler(this.requestAuthProviderHandler);
}
- load(): void {}
-
private async authSessionAction(data: ISessionAction | null, contexts: IExecutionContextProvider) {
const action = contexts.getContext(sessionActionContext);
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeService.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeService.ts
new file mode 100644
index 0000000000..891de97644
--- /dev/null
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeService.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 { injectable } from '@cloudbeaver/core-di';
+import { Executor, type IExecutor } from '@cloudbeaver/core-executor';
+
+export interface IElementsTreeLoadData {
+ nodeId: string;
+ manual: boolean;
+}
+
+@injectable()
+export class ElementsTreeService {
+ readonly onLoad: IExecutor;
+
+ constructor() {
+ this.onLoad = new Executor();
+ }
+}
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
index 7ef4442738..1858edc4fe 100644
--- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
+++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts
@@ -12,13 +12,14 @@ import { getComputed, IFolderExplorerContext, useExecutor, useObjectRef, useObse
import { ConnectionInfoActiveProjectKey, ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
-import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
+import { ExecutorInterrupter, ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import { type NavNode, NavNodeInfoResource, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
import { CachedMapAllKey, CachedResourceOffsetPageKey, getNextPageOffset, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import type { IDNDData } from '@cloudbeaver/core-ui';
import { ILoadableState, MetadataMap, throttle } from '@cloudbeaver/core-utils';
+import { ElementsTreeService } from './ElementsTreeService';
import type { IElementsTreeAction } from './IElementsTreeAction';
import type { INavTreeNodeInfo } from './INavTreeNodeInfo';
import type { NavigationNodeRendererComponent } from './NavigationNodeComponent';
@@ -137,6 +138,7 @@ export function useElementsTree(options: IOptions): IElementsTree {
const navNodeInfoResource = useService(NavNodeInfoResource);
const navTreeResource = useService(NavTreeResource);
const connectionInfoResource = useService(ConnectionInfoResource);
+ const elementsTreeService = useService(ElementsTreeService);
const [localTreeNodesState] = useState(
() =>
@@ -156,6 +158,12 @@ export function useElementsTree(options: IOptions): IElementsTree {
async function handleLoadChildren(id: string, manual: boolean): Promise {
try {
+ const context = await elementsTreeService.onLoad.execute({ nodeId: id, manual });
+
+ if (ExecutorInterrupter.isInterrupted(context)) {
+ return false;
+ }
+
return await options.loadChildren(id, manual);
} catch (exception: any) {
notificationService.logException(exception);
diff --git a/webapp/packages/plugin-navigation-tree/src/index.ts b/webapp/packages/plugin-navigation-tree/src/index.ts
index 9f2df7770f..1a763288d1 100644
--- a/webapp/packages/plugin-navigation-tree/src/index.ts
+++ b/webapp/packages/plugin-navigation-tree/src/index.ts
@@ -26,6 +26,7 @@ export * from './NavigationTree/ElementsTree/useElementsTree';
export * from './NavigationTree/ElementsTree/ElementsTreeTools/MENU_ELEMENTS_TREE_TOOLS';
export * from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService';
export * from './NavigationTree/ElementsTree/elementsTreeNameFilter';
+export * from './NavigationTree/ElementsTree/ElementsTreeService';
export * from './NavigationTree/NavigationTreeBootstrap';
export * from './NavigationTree/NavigationTreeService';
export { default as ElementsTreeToolsStyles } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeTools.m.css';
diff --git a/webapp/packages/plugin-navigation-tree/src/manifest.ts b/webapp/packages/plugin-navigation-tree/src/manifest.ts
index a6c87d5c80..6cc907c4ec 100644
--- a/webapp/packages/plugin-navigation-tree/src/manifest.ts
+++ b/webapp/packages/plugin-navigation-tree/src/manifest.ts
@@ -8,6 +8,7 @@
import type { PluginManifest } from '@cloudbeaver/core-di';
import { LocaleService } from './LocaleService';
+import { ElementsTreeService } from './NavigationTree/ElementsTree/ElementsTreeService';
import { ElementsTreeToolsMenuService } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenuService';
import { ElementsTreeSettingsService } from './NavigationTree/ElementsTree/ElementsTreeTools/NavigationTreeSettings/ElementsTreeSettingsService';
import { NavigationTreeBootstrap } from './NavigationTree/NavigationTreeBootstrap';
@@ -27,5 +28,6 @@ export const navigationTreePlugin: PluginManifest = {
NavNodeViewService,
ElementsTreeSettingsService,
NavigationTreeSettingsService,
+ ElementsTreeService,
],
};
diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts
index f778fd15ab..6c514ee951 100644
--- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts
+++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/PluginBootstrap.ts
@@ -14,7 +14,7 @@ import { isResourceOfType, ProjectInfoResource, ProjectsService } from '@cloudbe
import { CachedMapAllKey, CachedTreeChildrenKey } from '@cloudbeaver/core-resource';
import { getRmResourcePath, NAV_NODE_TYPE_RM_RESOURCE, ResourceManagerResource, RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
import { createPath, getPathName } from '@cloudbeaver/core-utils';
-import { ACTION_SAVE, ActionService, DATA_CONTEXT_MENU, KEY_BINDING_SAVE, KeyBindingService, MenuService } from '@cloudbeaver/core-view';
+import { ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view';
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { getResourceKeyFromNodeId } from '@cloudbeaver/plugin-navigation-tree-rm';
import { RESOURCE_NAME_REGEX, ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
@@ -55,7 +55,6 @@ export class PluginBootstrap extends Bootstrap {
private readonly sqlEditorSettingsService: SqlEditorSettingsService,
private readonly resourceManagerResource: ResourceManagerResource,
private readonly resourceManagerScriptsService: ResourceManagerScriptsService,
- private readonly keyBindingService: KeyBindingService,
) {
super();
}
@@ -83,10 +82,6 @@ export class PluginBootstrap extends Bootstrap {
return dataSource instanceof MemorySqlDataSource || dataSource instanceof LocalStorageSqlDataSource;
}
- if (action === ACTION_SAVE) {
- return dataSource?.isAutoSaveEnabled === false;
- }
-
return false;
},
handler: async (context, action) => {
@@ -187,34 +182,9 @@ export class PluginBootstrap extends Bootstrap {
}
}
}
-
- if (action === ACTION_SAVE) {
- const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
- const source = this.sqlDataSourceService.get(state.editorId) as ResourceSqlDataSource | undefined;
-
- if (!source) {
- return;
- }
-
- await source.save();
- }
- },
- isDisabled: (context, action) => {
- if (action === ACTION_SAVE) {
- const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
- const source = this.sqlDataSourceService.get(state.editorId) as ResourceSqlDataSource | undefined;
-
- if (!source) {
- return true;
- }
-
- return source.isLoading() || source.isSaved;
- }
-
- return false;
},
getActionInfo: (context, action) => {
- if (action === ACTION_SAVE_AS_SCRIPT || action === ACTION_SAVE) {
+ if (action === ACTION_SAVE_AS_SCRIPT) {
return {
...action.info,
label: '',
@@ -241,23 +211,7 @@ export class PluginBootstrap extends Bootstrap {
!!dataSource?.hasFeature(ESqlDataSourceFeatures.script)
);
},
- getItems: (context, items) => [...items, ACTION_SAVE_AS_SCRIPT, ACTION_SAVE],
- });
-
- this.keyBindingService.addKeyBindingHandler({
- id: 'script-save',
- binding: KEY_BINDING_SAVE,
- isBindingApplicable: (context, action) => action === ACTION_SAVE,
- handler: async context => {
- const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
- const source = this.sqlDataSourceService.get(state.editorId) as ResourceSqlDataSource | undefined;
-
- if (!source) {
- return;
- }
-
- await source.save();
- },
+ getItems: (context, items) => [...items, ACTION_SAVE_AS_SCRIPT],
});
}
diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts
index a724107bb6..1d7f71fd93 100644
--- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts
+++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSource.ts
@@ -17,16 +17,13 @@ import { TaskScheduler } from '@cloudbeaver/core-executor';
import type { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { isResourceAlias, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-resource';
import { getRmResourceKey, ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
+import type { NetworkStateService } from '@cloudbeaver/core-root';
import { debounce, getPathName, isArraysEqual, isNotNullDefined, isObjectsEqual, isValuesEqual } from '@cloudbeaver/core-utils';
import { SCRIPTS_TYPE_ID } from '@cloudbeaver/plugin-resource-manager-scripts';
import { BaseSqlDataSource, ESqlDataSourceFeatures, SqlEditorService } from '@cloudbeaver/plugin-sql-editor';
import type { IResourceSqlDataSourceState } from './IResourceSqlDataSourceState';
-interface IResourceInfo {
- isReadonly?: (dataSource: ResourceSqlDataSource) => boolean;
-}
-
interface IResourceActions {
rename(dataSource: ResourceSqlDataSource, key: string, name: string): Promise;
read(dataSource: ResourceSqlDataSource, key: string): Promise;
@@ -103,7 +100,6 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
}
private actions?: IResourceActions;
- private info?: IResourceInfo;
private lastAction: (() => Promise) | undefined;
private state!: IResourceSqlDataSourceState;
@@ -112,6 +108,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
private resourceUseKeyId: string | null;
constructor(
+ private readonly networkStateService: NetworkStateService,
private readonly projectInfoResource: ProjectInfoResource,
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly resourceManagerResource: ResourceManagerResource,
@@ -144,10 +141,22 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
}
isReadonly(): boolean {
- return !this.isLoaded() || this.info?.isReadonly?.(this) === true;
+ if (!this.projectId || !this.networkStateService.state) {
+ return true;
+ }
+
+ const project = this.projectInfoResource.get(this.projectId);
+
+ return !this.isLoaded() || !project?.canEditResources;
}
isOutdated(): boolean {
+ if (this.projectId) {
+ if (this.projectInfoResource.isOutdated(this.projectId)) {
+ return true;
+ }
+ }
+
return this.resourceKey !== undefined && super.isOutdated();
}
@@ -189,10 +198,6 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
this.actions = actions;
}
- setInfo(info?: IResourceInfo): void {
- this.info = info;
- }
-
setName(name: string | null): void {
name = name?.trim() ?? null;
if (!name || name === this.name) {
@@ -267,6 +272,11 @@ export class ResourceSqlDataSource extends BaseSqlDataSource {
if (this.state.resourceKey && !this.resourceUseKeyId) {
this.resourceUseKeyId = this.resourceManagerResource.useTracker.use(this.state.resourceKey);
}
+
+ if (this.projectId) {
+ await this.projectInfoResource.load(this.projectId);
+ }
+
await this.read();
}
diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts
index fb006be0b6..8f807ee0fa 100644
--- a/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts
+++ b/webapp/packages/plugin-sql-editor-navigation-tab-script/src/ResourceSqlDataSourceBootstrap.ts
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
-import { action, makeObservable, observable, untracked } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
import { ConfirmationDialog } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
@@ -13,11 +13,11 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
-import { CachedMapAllKey, resourceKeyList, ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
-import { getRmResourceKey, IResourceManagerMoveData, ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
-import { NetworkStateService, WindowEventsService } from '@cloudbeaver/core-root';
+import { resourceKeyList, ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
+import { IResourceManagerMoveData, ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
+import { NetworkStateService } from '@cloudbeaver/core-root';
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
-import { createPath, getPathParent, throttle } from '@cloudbeaver/core-utils';
+import { createPath, getPathParent } from '@cloudbeaver/core-utils';
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
import { NavResourceNodeService } from '@cloudbeaver/plugin-navigation-tree-rm';
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
@@ -30,7 +30,6 @@ import { ResourceSqlDataSource } from './ResourceSqlDataSource';
import { SqlEditorTabResourceService } from './SqlEditorTabResourceService';
const RESOURCE_TAB_STATE = 'sql_editor_resource_tab_state';
-const SYNC_DELAY = 5 * 60 * 1000;
@injectable()
export class ResourceSqlDataSourceBootstrap extends Bootstrap {
@@ -49,14 +48,12 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
private readonly resourceManagerResource: ResourceManagerResource,
private readonly resourceManagerScriptsService: ResourceManagerScriptsService,
private readonly projectInfoResource: ProjectInfoResource,
- private readonly windowEventsService: WindowEventsService,
private readonly sqlEditorTabResourceService: SqlEditorTabResourceService,
private readonly sqlEditorService: SqlEditorService,
localStorageSaveService: LocalStorageSaveService,
) {
super();
this.dataSourceStateState = new Map();
- this.focusChangeHandler = throttle(this.focusChangeHandler.bind(this), SYNC_DELAY, false);
makeObservable(this, {
createState: action,
@@ -87,7 +84,6 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
}
register(): void | Promise {
- this.windowEventsService.onFocusChange.addHandler(this.focusChangeHandler.bind(this));
this.resourceManagerResource.onItemDelete.addHandler(this.resourceDeleteHandler.bind(this));
this.resourceManagerResource.onMove.addHandler(this.resourceMoveHandler.bind(this));
@@ -95,6 +91,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
key: ResourceSqlDataSource.key,
getDataSource: (editorId, options) => {
const dataSource = new ResourceSqlDataSource(
+ this.networkStateService,
this.projectInfoResource,
this.connectionInfoResource,
this.resourceManagerResource,
@@ -118,20 +115,6 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
setProperties: this.setProperties.bind(this),
});
- dataSource.setInfo({
- isReadonly: (dataSource: ResourceSqlDataSource) => {
- if (!dataSource.resourceKey) {
- return true;
- }
- const resourceKey = getRmResourceKey(dataSource.resourceKey);
-
- untracked(() => this.projectInfoResource.load(CachedMapAllKey));
- const project = this.projectInfoResource.get(resourceKey.projectId);
-
- return !this.networkStateService.state || !project?.canEditResources;
- },
- });
-
return dataSource;
},
onDestroy: (_, editorId) => this.deleteState(editorId),
@@ -188,22 +171,6 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap {
this.dataSourceStateState.delete(editorId);
}
- private async focusChangeHandler(focused: boolean) {
- if (!this.resourceManagerService.enabled) {
- return;
- }
-
- if (focused) {
- const dataSources = this.sqlDataSourceService.dataSources
- .filter(([, dataSource]) => dataSource instanceof ResourceSqlDataSource)
- .map(([, dataSource]) => dataSource as ResourceSqlDataSource);
-
- for (const dataSource of dataSources) {
- dataSource.markOutdated();
- }
- }
- }
-
private resourceMoveHandler(data: IResourceManagerMoveData) {
if (!this.resourceManagerService.enabled) {
return;
diff --git a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
index 7b8540f542..e993177ea2 100644
--- a/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
+++ b/webapp/packages/plugin-sql-editor/src/MenuBootstrap.ts
@@ -7,7 +7,21 @@
*/
import type { IDataContextProvider } from '@cloudbeaver/core-data-context';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
-import { ACTION_REDO, ACTION_UNDO, ActionService, IAction, KEY_BINDING_REDO, KEY_BINDING_UNDO, KeyBindingService } from '@cloudbeaver/core-view';
+import { WindowEventsService } from '@cloudbeaver/core-root';
+import { throttle } from '@cloudbeaver/core-utils';
+import {
+ ACTION_REDO,
+ ACTION_SAVE,
+ ACTION_UNDO,
+ ActionService,
+ DATA_CONTEXT_MENU,
+ IAction,
+ KEY_BINDING_REDO,
+ KEY_BINDING_SAVE,
+ KEY_BINDING_UNDO,
+ KeyBindingService,
+ MenuService,
+} from '@cloudbeaver/core-view';
import { ACTION_SQL_EDITOR_EXECUTE } from './actions/ACTION_SQL_EDITOR_EXECUTE';
import { ACTION_SQL_EDITOR_EXECUTE_NEW } from './actions/ACTION_SQL_EDITOR_EXECUTE_NEW';
@@ -20,16 +34,114 @@ import { KEY_BINDING_SQL_EDITOR_EXECUTE_NEW } from './actions/bindings/KEY_BINDI
import { KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_EXECUTE_SCRIPT';
import { KEY_BINDING_SQL_EDITOR_FORMAT } from './actions/bindings/KEY_BINDING_SQL_EDITOR_FORMAT';
import { KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN } from './actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_EXECUTION_PLAN';
+import { DATA_CONTEXT_SQL_EDITOR_STATE } from './DATA_CONTEXT_SQL_EDITOR_STATE';
import { ESqlDataSourceFeatures } from './SqlDataSource/ESqlDataSourceFeatures';
+import { SqlDataSourceService } from './SqlDataSource/SqlDataSourceService';
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './SqlEditor/DATA_CONTEXT_SQL_EDITOR_DATA';
+import { SQL_EDITOR_TOOLS_MENU } from './SqlEditor/SQL_EDITOR_TOOLS_MENU';
+
+const SYNC_DELAY = 5 * 60 * 1000;
@injectable()
export class MenuBootstrap extends Bootstrap {
- constructor(private readonly actionService: ActionService, private readonly keyBindingService: KeyBindingService) {
+ constructor(
+ private readonly menuService: MenuService,
+ private readonly actionService: ActionService,
+ private readonly keyBindingService: KeyBindingService,
+ private readonly sqlDataSourceService: SqlDataSourceService,
+ private readonly windowEventsService: WindowEventsService,
+ ) {
super();
}
register(): void {
+ this.windowEventsService.onFocusChange.addHandler(throttle(this.focusChangeHandler.bind(this), SYNC_DELAY, false));
+ this.actionService.addHandler({
+ id: 'sql-editor-base-handler',
+ isActionApplicable: (context, action): boolean => {
+ const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
+
+ if (!state) {
+ return false;
+ }
+
+ const dataSource = this.sqlDataSourceService.get(state.editorId);
+
+ if (action === ACTION_SAVE) {
+ return dataSource?.isAutoSaveEnabled === false;
+ }
+
+ return false;
+ },
+ handler: async (context, action) => {
+ if (action === ACTION_SAVE) {
+ const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
+ const source = this.sqlDataSourceService.get(state.editorId);
+
+ if (!source) {
+ return;
+ }
+
+ await source.save();
+ }
+ },
+ isDisabled: (context, action) => {
+ if (action === ACTION_SAVE) {
+ const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
+ const source = this.sqlDataSourceService.get(state.editorId);
+
+ if (!source) {
+ return true;
+ }
+
+ return source.isLoading() || source.isSaved || source.isReadonly();
+ }
+
+ return false;
+ },
+ getActionInfo: (context, action) => {
+ if (action === ACTION_SAVE) {
+ return {
+ ...action.info,
+ label: '',
+ };
+ }
+
+ return action.info;
+ },
+ });
+
+ this.menuService.addCreator({
+ isApplicable: context => {
+ const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
+
+ if (!state) {
+ return false;
+ }
+
+ const dataSource = this.sqlDataSourceService.get(state.editorId);
+
+ return context.get(DATA_CONTEXT_MENU) === SQL_EDITOR_TOOLS_MENU && !!dataSource?.hasFeature(ESqlDataSourceFeatures.script);
+ },
+ getItems: (context, items) => [...items, ACTION_SAVE],
+ });
+
+ this.keyBindingService.addKeyBindingHandler({
+ id: 'sql-editor-save',
+ binding: KEY_BINDING_SAVE,
+ isBindingApplicable: (context, action) => action === ACTION_SAVE,
+ handler: async context => {
+ const state = context.get(DATA_CONTEXT_SQL_EDITOR_STATE);
+ const source = this.sqlDataSourceService.get(state.editorId);
+
+ if (!source) {
+ return;
+ }
+
+ await source.save();
+ },
+ });
+
this.actionService.addHandler({
id: 'sql-editor-actions',
isActionApplicable: (contexts, action): boolean => {
@@ -179,5 +291,13 @@ export class MenuBootstrap extends Bootstrap {
}
}
- load(): void | Promise {}
+ private async focusChangeHandler(focused: boolean) {
+ if (focused) {
+ const dataSources = this.sqlDataSourceService.dataSources.values();
+
+ for (const [_, dataSource] of dataSources) {
+ dataSource.markOutdated();
+ }
+ }
+ }
}