diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java index 442b47d84e..279c036d15 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBApplication.java @@ -174,7 +174,7 @@ public class CBApplication extends BaseApplicationImpl { try { loadConfiguration(configPath); - File runtimeConfigFile = getRuntimeConfigFile(); + File runtimeConfigFile = getRuntimeAppConfigFile(); if (runtimeConfigFile.exists()) { log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]"); parseConfiguration(runtimeConfigFile); @@ -327,8 +327,13 @@ public class CBApplication extends BaseApplicationImpl { } @NotNull - private File getRuntimeConfigFile() { - return new File(getDataDirectory(false), CBConstants.RUNTIME_CONFIG_FILE_NAME); + private File getRuntimeAppConfigFile() { + return new File(getDataDirectory(false), CBConstants.RUNTIME_APP_CONFIG_FILE_NAME); + } + + @NotNull + private File getRuntimeProductConfigFile() { + return new File(getDataDirectory(false), CBConstants.RUNTIME_PRODUCT_CONFIG_FILE_NAME); } @NotNull @@ -439,6 +444,19 @@ public class CBApplication extends BaseApplicationImpl { } } } + + // Add product config from runtime + { + File rtConfig = getRuntimeProductConfigFile(); + if (rtConfig.exists()) { + log.debug("Load product runtime configuration from '" + rtConfig.getAbsolutePath() + "'"); + try (Reader reader = new InputStreamReader(new FileInputStream(rtConfig), StandardCharsets.UTF_8)) { + productConfiguration.putAll(JSONUtils.parseMap(gson, reader)); + } catch (Exception e) { + log.error("Error reading product runtime configuration", e); + } + } + } } static String getRelativePath(String path, String curDir) { @@ -530,7 +548,7 @@ public class CBApplication extends BaseApplicationImpl { // Re-load runtime configuration try { log.debug("Reloading application configuration"); - File runtimeConfigFile = getRuntimeConfigFile(); + File runtimeConfigFile = getRuntimeAppConfigFile(); if (runtimeConfigFile.exists()) { log.debug("Runtime configuration [" + runtimeConfigFile.getAbsolutePath() + "]"); parseConfiguration(runtimeConfigFile); @@ -567,7 +585,7 @@ public class CBApplication extends BaseApplicationImpl { private void saveRuntimeConfig(String newServerName, long sessionExpireTime, CBAppConfig appConfig) throws DBException { - File runtimeConfigFile = getRuntimeConfigFile(); + File runtimeConfigFile = getRuntimeAppConfigFile(); try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) { Gson gson = new GsonBuilder() .setLenient() diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java index 76a2fba9df..fbae82e57c 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBConstants.java @@ -30,7 +30,8 @@ public class CBConstants { public static final String DEFAULT_CONFIG_FILE_PATH = "/etc/cloudbeaver.conf"; public static final String RUNTIME_DATA_DIR_NAME = ".data"; - public static final String RUNTIME_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf"; + public static final String RUNTIME_APP_CONFIG_FILE_NAME = ".cloudbeaver.runtime.conf"; + public static final String RUNTIME_PRODUCT_CONFIG_FILE_NAME = ".product.runtime.conf"; public static final String CLI_PARAM_WEB_CONFIG = "-web-config"; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index 5d8d6a955b..0629f69c7a 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -274,6 +274,8 @@ public class WebServiceCore implements DBWServiceCore { @Override public WebConnectionInfo updateConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig config) throws DBWebException { + // Do not check for custom connection option. Already created connections can be edited. + // Also template connections can be edited if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) { throw new DBWebException("Connection edit is restricted by server configuration"); } diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index a17e14f187..2ceff99379 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -16,6 +16,7 @@ export * from './shared/NodesManager/NavNodeInfoResource'; export * from './shared/NodesManager/NavNodeManagerService'; export * from './shared/NodesManager/NavTreeResource'; export * from './shared/NodesManager/NavNodeExtensionsService'; +export * from './shared/NodesManager/NodeLink'; export * from './shared/NodesManager/NodeManagerUtils'; export * from './shared/NavigationTabs/NavigationTabsService'; export * from './shared/NavigationTabs/TabNavigationContext'; diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index d5d6ec5d13..9cf48d0863 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -284,6 +284,10 @@ export class NavNodeManagerService extends Bootstrap { const parents: string[] = []; let parent = this.getNode(nodeId); + if (!parent) { + return NodeManagerUtils.parentsFromPath(nodeId); + } + while (parent && parent.parentId !== ROOT_NODE_PATH && parent.id !== parent.parentId) { parents.unshift(parent.parentId); parent = this.getNode(parent.parentId); diff --git a/webapp/packages/core-app/src/shared/NodesManager/NodeLink.tsx b/webapp/packages/core-app/src/shared/NodesManager/NodeLink.tsx new file mode 100644 index 0000000000..08883791ea --- /dev/null +++ b/webapp/packages/core-app/src/shared/NodesManager/NodeLink.tsx @@ -0,0 +1,47 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 React, { useCallback } from 'react'; +import styled from 'reshadow'; + +import { Link } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { css } from '@reshadow/react'; + +import { NavigationTreeService } from '../../NavigationTree/NavigationTreeService'; +import { parseNodeParentId } from './NavNodeManagerService'; + +const style = css` + Link { + cursor: pointer; + } +`; + +interface Props { + name: string; + nodeId?: string; +} + +// TODO: we probably can get name from NavNodeInfoResource but element can be not loaded +export const NodeLink: React.FC = function NodeLink({ children, name, nodeId }) { + const navigationTreeService = useService(NavigationTreeService); + + const handleClick = useCallback(() => { + if (nodeId) { + navigationTreeService.navToNode(nodeId, parseNodeParentId(nodeId)); + } + }, + [nodeId, navigationTreeService] + ); + + if (nodeId) { + return styled(style)({children}); + } + + return <>{children}; +}; diff --git a/webapp/packages/core-app/src/shared/NodesManager/NodeManagerUtils.ts b/webapp/packages/core-app/src/shared/NodesManager/NodeManagerUtils.ts index fe40f3ea33..4833568ef2 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NodeManagerUtils.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NodeManagerUtils.ts @@ -16,6 +16,25 @@ export const NodeManagerUtils = { return connectionNodeId.replace('database://', ''); }, + parentsFromPath(nodeId: string): string[] { + const parts = nodeId + .replace('//', '\\') + .split('/'); + let lastPath = ''; + const parents: string[] = []; + + for (const part of parts) { + if (lastPath !== '') { + lastPath += '/'; + } + lastPath += part.replace('\\', '//'); + + parents.push(lastPath); + } + + return parents; + }, + connectionIdToConnectionNodeId(connectionId: string): string { return `database://${connectionId}`; }, diff --git a/webapp/packages/core-theming/src/styles/_link.scss b/webapp/packages/core-theming/src/styles/_link.scss index 891ba5b257..7b650f12b1 100644 --- a/webapp/packages/core-theming/src/styles/_link.scss +++ b/webapp/packages/core-theming/src/styles/_link.scss @@ -11,7 +11,7 @@ color: $link-color; text-decoration: none; - &:href, + &:hover, &:focus { color: $link-color-focus; text-decoration: underline;