diff --git a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf index 57d7546bfe..168e296f93 100644 --- a/config/sample-databases/DefaultConfiguration/cloudbeaver.conf +++ b/config/sample-databases/DefaultConfiguration/cloudbeaver.conf @@ -39,6 +39,8 @@ anonymousUserRole: "user", supportsCustomConnections: false, + forwardProxy: false, + publicCredentialsSaveEnabled: true, adminCredentialsSaveEnabled: true, diff --git a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf index 30fbf76216..a1f14be291 100644 --- a/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf +++ b/config/sample-databases/SQLiteConfiguration/cloudbeaver.conf @@ -39,6 +39,8 @@ anonymousUserRole: "user", supportsCustomConnections: false, + forwardProxy: false, + publicCredentialsSaveEnabled: true, adminCredentialsSaveEnabled: true, diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index ab0df10d21..f0e52880c3 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -49,6 +49,8 @@ public class LocalResourceController implements RMController { private static final Log log = Log.getLog(LocalResourceController.class); + private static final String FILE_REGEX = "(?U)[\\w.$() -]+"; + private static final String PROJECT_PREFIX_GLOBAL = "g_"; private static final String PROJECT_PREFIX_SHARED = "s_"; private static final String PROJECT_PREFIX_USER = "u_"; @@ -182,6 +184,7 @@ public class LocalResourceController implements RMController { @NotNull String resourcePath, boolean isFolder) throws DBException { + validateResourcePath(resourcePath); Path targetPath = getTargetPath(projectId, resourcePath); if (Files.exists(targetPath)) { throw new DBException("Resource '" + resourcePath + "' already exists"); @@ -205,6 +208,7 @@ public class LocalResourceController implements RMController { @Override public void deleteResource(@NotNull String projectId, @NotNull String resourcePath, boolean recursive) throws DBException { + validateResourcePath(resourcePath); Path targetPath = getTargetPath(projectId, resourcePath); if (!Files.exists(targetPath)) { throw new DBException("Resource '" + resourcePath + "' doesn't exists"); @@ -227,10 +231,11 @@ public class LocalResourceController implements RMController { public RMResource[] getResourcePath(@NotNull String projectId, @NotNull String resourcePath) throws DBException { return makeResourcePath(projectId, getTargetPath(projectId, resourcePath)).toArray(RMResource[]::new); } - + @NotNull @Override public byte[] getResourceContents(@NotNull String projectId, @NotNull String resourcePath) throws DBException { + validateResourcePath(resourcePath); Path targetPath = getTargetPath(projectId, resourcePath); if (!Files.exists(targetPath)) { throw new DBException("Resource '" + resourcePath + "' doesn't exists"); @@ -249,6 +254,7 @@ public class LocalResourceController implements RMController { @NotNull String resourcePath, @NotNull byte[] data) throws DBException { + validateResourcePath(resourcePath); Number fileSizeLimit = WebAppUtils.getWebApplication() .getAppConfiguration() .getResourceQuota(WebSQLConstants.QUOTA_PROP_RM_FILE_SIZE_LIMIT); @@ -272,6 +278,16 @@ public class LocalResourceController implements RMController { return DEFAULT_CHANGE_ID; } + private void validateResourcePath(String resourcePath) throws DBException { + if (resourcePath.startsWith(".")) { + throw new DBException("Resource path '" + resourcePath + "' can't start with dot"); + } + if (!resourcePath.matches(FILE_REGEX)) { + String illegalCharacters = resourcePath.replaceAll(FILE_REGEX, " ").strip(); + throw new DBException("Resource path '" + resourcePath + "' contains illegal characters: " + illegalCharacters); + } + } + @Override public void addRMEventListener(RMEventListener listener) { this.listeners.add(listener); @@ -307,7 +323,7 @@ public class LocalResourceController implements RMController { if (!targetPath.startsWith(projectPath)) { throw new DBException("Invalid resource path"); } - return targetPath; + return WebAppUtils.getWebApplication().getHomeDirectory().relativize(targetPath); } catch (InvalidPathException e) { throw new DBException("Resource path contains invalid characters"); } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index 90709243ba..0ae1d6e0d6 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -546,7 +546,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, } clearAuthTokens(); this.sessionAuthContext.close(); - this.user = null; + setUser(null); if (this.sessionProject != null) { this.sessionProject.dispose(); @@ -760,7 +760,7 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, } if (application.isConfigurationMode() && this.user == null && newUser != null) { //FIXME hotfix to avoid exception after external auth provider login in easy config - this.user = newUser; + setUser(newUser); refreshUserData(); } else if (!CommonUtils.equalObjects(this.user, newUser)) { throw new DBException("Can't authorize different users in the single session"); @@ -912,10 +912,14 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, this.adminSecurityController = application.getAdminSecurityController(this); this.rmController = application.getResourceController(this); - this.user = tokenInfo.getUserId() == null ? null : new WebUser(securityController.getUserById(tokenInfo.getUserId())); + setUser(tokenInfo.getUserId() == null ? null : new WebUser(securityController.getUserById(tokenInfo.getUserId()))); refreshUserData(); } + private synchronized void setUser(@Nullable WebUser user) { + this.user = user; + } + @Override public SMCredentials getActiveUserCredentials() { return smCredentials; diff --git a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF index 0df6e252a4..11458ae9a0 100644 --- a/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.server/META-INF/MANIFEST.MF @@ -26,7 +26,6 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport, io.cloudbeaver.model;visibility:=reexport, io.cloudbeaver.service.security;visibility:=reexport Export-Package: io.cloudbeaver, - io.cloudbeaver.auth.provider.fa, io.cloudbeaver.model, io.cloudbeaver.model.resources, io.cloudbeaver.model.user, diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java index 821146bfe3..ada59adda2 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebServiceUtils.java @@ -33,6 +33,7 @@ import org.jkiss.dbeaver.model.access.DBAAuthCredentials; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.connection.DBPDriver; +import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.model.navigator.DBNModel; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; @@ -245,6 +246,7 @@ public class WebServiceUtils extends WebCommonUtils { if (authProperties == null) { authProperties = new LinkedHashMap<>(); } + authProperties.replace(AuthModelDatabaseNativeCredentials.PROP_USER_PASSWORD, null); } else { if (authProperties == null) { // No changes diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java index b873464f2f..10f9e129d4 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBAppConfig.java @@ -46,6 +46,7 @@ public class CBAppConfig extends BaseWebAppConfiguration { private boolean supportsConnectionBrowser; private boolean supportsUserWorkspaces; private boolean enableReverseProxyAuth; + private boolean forwardProxy; private boolean publicCredentialsSaveEnabled; private boolean adminCredentialsSaveEnabled; @@ -79,6 +80,7 @@ public class CBAppConfig extends BaseWebAppConfiguration { this.authConfiguration = new LinkedHashMap<>(); this.resourceQuotas = new LinkedHashMap<>(); this.enableReverseProxyAuth = false; + this.forwardProxy = false; } public CBAppConfig(CBAppConfig src) { @@ -98,6 +100,7 @@ public class CBAppConfig extends BaseWebAppConfiguration { this.authConfiguration = new LinkedHashMap<>(src.authConfiguration); this.resourceQuotas = new LinkedHashMap<>(src.resourceQuotas); this.enableReverseProxyAuth = src.enableReverseProxyAuth; + this.forwardProxy = src.forwardProxy; } @@ -310,4 +313,11 @@ public class CBAppConfig extends BaseWebAppConfiguration { return enableReverseProxyAuth; } + //////////////////////////////////////////// + // Forward proxy + + public boolean isEnabledForwardProxy() { + return forwardProxy; + } + } 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 258c0d7f5d..e84c417658 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 @@ -778,6 +778,7 @@ public class CBApplication extends BaseWebApplication { JSONUtils.field(json, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled()); JSONUtils.field(json, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled()); JSONUtils.field(json, "enableReverseProxyAuth", appConfig.isEnabledReverseProxyAuth()); + JSONUtils.field(json, "forwardProxy", appConfig.isEnabledForwardProxy()); JSONUtils.field(json, CBConstants.PARAM_RESOURCE_MANAGER_ENABLED, appConfig.isResourceManagerEnabled()); Map resourceQuotas = appConfig.getResourceQuotas(); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java index 803cf03a5b..306c645c6a 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/jetty/CBJettyServer.java @@ -93,12 +93,17 @@ public class CBJettyServer { } + boolean forwardProxy = application.getAppConfiguration().isEnabledForwardProxy(); { // HTTP config for(Connector y : server.getConnectors()) { for(ConnectionFactory x : y.getConnectionFactories()) { if(x instanceof HttpConnectionFactory) { - ((HttpConnectionFactory)x).getHttpConfiguration().setSendServerVersion(false); + HttpConfiguration httpConfiguration = ((HttpConnectionFactory)x).getHttpConfiguration(); + httpConfiguration.setSendServerVersion(false); + if (forwardProxy) { + httpConfiguration.addCustomizer(new ForwardedRequestCustomizer()); + } } } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java index b93c9e8ba7..b8f0aefec4 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/WebDatabaseObjectInfo.java @@ -168,6 +168,7 @@ public class WebDatabaseObjectInfo { } private static void getObjectFeatures(DBSObject object, List features) { + boolean isDiagramSupported = true; if (object instanceof DBPScriptObject) features.add(OBJECT_FEATURE_SCRIPT); if (object instanceof DBPScriptObjectExt) features.add(OBJECT_FEATURE_SCRIPT_EXTENDED); if (object instanceof DBSDataContainer) { @@ -175,9 +176,14 @@ public class WebDatabaseObjectInfo { if (((DBSDataContainer) object).isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER)) { features.add(OBJECT_FEATURE_DATA_CONTAINER_SUPPORTS_FILTERS); } + if (((DBSDataContainer) object).isFeatureSupported(DBSDataContainer.FEATURE_KEY_VALUE)) { + isDiagramSupported = false; + } } if (object instanceof DBSDataManipulator) features.add(OBJECT_FEATURE_DATA_MANIPULATOR); - if (object instanceof DBSEntity && !(object instanceof DBSDataType)) features.add(OBJECT_FEATURE_ENTITY); + if (object instanceof DBSEntity && !(object instanceof DBSDataType) && isDiagramSupported) { + features.add(OBJECT_FEATURE_ENTITY); + } if (object instanceof DBSSchema) features.add(OBJECT_FEATURE_SCHEMA); if (object instanceof DBSCatalog) features.add(OBJECT_FEATURE_CATALOG); if (object instanceof DBSObjectContainer) { 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 2b3d2477e6..3ad8be1f74 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 @@ -154,7 +154,9 @@ public class WebNavigatorNodeInfo { features.add("canDelete"); } if (objectManager instanceof DBEObjectRenamer && ((DBEObjectRenamer) objectManager).canRenameObject(object)) { - features.add("canRename"); + if (!object.getDataSource().getContainer().getNavigatorSettings().isShowOnlyEntities()) { + features.add("canRename"); + } } } } diff --git a/webapp/packages/core-app/public/apple-touch-icon.png b/webapp/packages/core-app/public/apple-touch-icon.png new file mode 100644 index 0000000000..58b305de65 Binary files /dev/null and b/webapp/packages/core-app/public/apple-touch-icon.png differ diff --git a/webapp/packages/core-app/public/favicon-32x32.png b/webapp/packages/core-app/public/favicon-32x32.png new file mode 100644 index 0000000000..aa00352922 Binary files /dev/null and b/webapp/packages/core-app/public/favicon-32x32.png differ diff --git a/webapp/packages/core-app/public/favicon.ico b/webapp/packages/core-app/public/favicon.ico new file mode 100644 index 0000000000..21c92734c8 Binary files /dev/null and b/webapp/packages/core-app/public/favicon.ico differ diff --git a/webapp/packages/core-app/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx b/webapp/packages/core-app/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx index 78bd71ba35..b714d44dbc 100644 --- a/webapp/packages/core-app/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx +++ b/webapp/packages/core-app/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx @@ -84,7 +84,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat onOpen={handleOpen} onSelect={handleSelect} > - + {/* */} {(expanded || expandedExternal) && } diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index 17006258c2..feaab27e9d 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -327,13 +327,7 @@ export class NavNodeManagerService extends Bootstrap { const nodeInfo = await contexts.getContext(this.navigationNavNodeContext); if (NodeManagerUtils.isDatabaseObject(nodeInfo.nodeId) && nodeInfo.connection) { - let connection: Connection | null; - try { - connection = await this.connectionsManagerService.requireConnection(nodeInfo.connection.id); - } catch (exception: any) { - this.notificationService.logException(exception); - throw exception; - } + const connection = await this.connectionsManagerService.requireConnection(nodeInfo.connection.id); if (!connection?.connected) { throw new Error('Connection not established'); diff --git a/webapp/packages/core-blocks/src/HTMLTemplates/head.html.ejs b/webapp/packages/core-blocks/src/HTMLTemplates/head.html.ejs index bf06afaa32..f07983153c 100644 --- a/webapp/packages/core-blocks/src/HTMLTemplates/head.html.ejs +++ b/webapp/packages/core-blocks/src/HTMLTemplates/head.html.ejs @@ -1,8 +1,12 @@ + + + + diff --git a/webapp/packages/core-connections/src/ConnectionsManagerService.ts b/webapp/packages/core-connections/src/ConnectionsManagerService.ts index bb5a30192b..578f42dbf1 100644 --- a/webapp/packages/core-connections/src/ConnectionsManagerService.ts +++ b/webapp/packages/core-connections/src/ConnectionsManagerService.ts @@ -35,10 +35,14 @@ export class ConnectionsManagerService { } async requireConnection(connectionId: string | null = null): Promise { - const context = await this.connectionExecutor.execute(connectionId); - const connection = context.getContext(this.connectionContext); + try { + const context = await this.connectionExecutor.execute(connectionId); + const connection = context.getContext(this.connectionContext); - return connection.connection; + return connection.connection; + } catch { + return null; + } } async addOpenedConnection(connection: Connection): Promise { @@ -50,7 +54,7 @@ export class ConnectionsManagerService { objectCatalogId?: string, objectSchemaId?: string ): ObjectContainer | undefined { - if (objectCatalogId){ + if (objectCatalogId) { const objectContainers = this.containerContainers.getCatalogData(connectionId, objectCatalogId); if (!objectContainers) { @@ -60,7 +64,7 @@ export class ConnectionsManagerService { if (!objectSchemaId) { return objectContainers.catalog; } - + return objectContainers.schemaList.find( objectContainer => objectContainer.name === objectSchemaId ); diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 73fc6823dc..57b0bef6a9 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -94,6 +94,10 @@ export class ServerConfigResource extends CachedDataResource; redirectOnFederatedAuth?: Maybe; + resourceManagerEnabled?: Maybe; resourceQuotas: Scalars['Object']; rootURI: Scalars['String']; serverURL: Scalars['String']; @@ -1365,6 +1366,7 @@ export interface ServerConfigInput { enabledAuthProviders?: InputMaybe>; enabledFeatures?: InputMaybe>; publicCredentialsSaveEnabled?: InputMaybe; + resourceManagerEnabled?: InputMaybe; serverName?: InputMaybe; serverURL?: InputMaybe; sessionExpireTime?: InputMaybe; @@ -2292,7 +2294,7 @@ export type ReadSessionLogQuery = { log: Array<{ time?: any; type: string; messa export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>; -export type ServerConfigQuery = { serverConfig: { name: string; version: string; workspaceId: string; serverURL: string; rootURI: string; hostName: string; productConfiguration: any; supportsCustomConnections?: boolean; supportsConnectionBrowser?: boolean; supportsWorkspaces?: boolean; sessionExpireTime?: number; anonymousAccessEnabled?: boolean; adminCredentialsSaveEnabled?: boolean; publicCredentialsSaveEnabled?: boolean; licenseRequired: boolean; licenseValid: boolean; configurationMode?: boolean; developmentMode?: boolean; redirectOnFederatedAuth?: boolean; enabledFeatures: Array; enabledAuthProviders: Array; resourceQuotas: any; disabledDrivers: Array; supportedLanguages: Array<{ isoCode: string; displayName?: string; nativeName?: string }>; defaultNavigatorSettings: { showSystemObjects: boolean; showUtilityObjects: boolean; showOnlyEntities: boolean; mergeEntities: boolean; hideFolders: boolean; hideSchemas: boolean; hideVirtualModel: boolean }; productInfo: { id: string; version: string; latestVersionInfo?: string; name: string; description?: string; buildTime: string; releaseTime: string; licenseInfo?: string } } }; +export type ServerConfigQuery = { serverConfig: { name: string; version: string; workspaceId: string; serverURL: string; rootURI: string; hostName: string; productConfiguration: any; supportsCustomConnections?: boolean; supportsConnectionBrowser?: boolean; supportsWorkspaces?: boolean; sessionExpireTime?: number; anonymousAccessEnabled?: boolean; adminCredentialsSaveEnabled?: boolean; publicCredentialsSaveEnabled?: boolean; resourceManagerEnabled?: boolean; licenseRequired: boolean; licenseValid: boolean; configurationMode?: boolean; developmentMode?: boolean; redirectOnFederatedAuth?: boolean; enabledFeatures: Array; enabledAuthProviders: Array; resourceQuotas: any; disabledDrivers: Array; supportedLanguages: Array<{ isoCode: string; displayName?: string; nativeName?: string }>; defaultNavigatorSettings: { showSystemObjects: boolean; showUtilityObjects: boolean; showOnlyEntities: boolean; mergeEntities: boolean; hideFolders: boolean; hideSchemas: boolean; hideVirtualModel: boolean }; productInfo: { id: string; version: string; latestVersionInfo?: string; name: string; description?: string; buildTime: string; releaseTime: string; licenseInfo?: string } } }; export type SessionPermissionsQueryVariables = Exact<{ [key: string]: never }>; @@ -3627,6 +3629,7 @@ export const ServerConfigDocument = ` anonymousAccessEnabled adminCredentialsSaveEnabled publicCredentialsSaveEnabled + resourceManagerEnabled licenseRequired licenseValid configurationMode diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index 33914c9127..5fce6cef5a 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -126,7 +126,11 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser - + ; readonly validationTask: IExecutor; readonly configurationContainer: PlaceholderContainer; - readonly pluginsContainer: PlaceholderContainer; + readonly pluginsContainer: PlaceholderContainer; private done: boolean; private stateLinked: boolean; diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx index 53705736d6..fedd57a88e 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectFolders.tsx @@ -77,11 +77,6 @@ export const ObjectFolders = observer(function ObjectFolders({ tab }) { }); const folders = navNodeViewService.getFolders(nodeId) || []; - - function openFolder(tabData: ITabData) { - navNodeManagerService.navToNode(nodeId, parentId, tabData.tabId); - } - const wrongFolder = ( !folders.includes(folderId) && folders.length > 0 @@ -94,6 +89,15 @@ export const ObjectFolders = observer(function ObjectFolders({ tab }) { folderId = folders[0]; } + function openFolder(tabData: ITabData) { + if (tabData.tabId === folderId) { + return; + } + + navNodeManagerService.navToNode(nodeId, parentId, tabData.tabId); + } + + useEffect(() => { if (wrongFolder) { navNodeManagerService.navToNode(nodeId, parentId, folderId); diff --git a/webapp/packages/plugin-resource-manager-administration/.gitignore b/webapp/packages/plugin-resource-manager-administration/.gitignore new file mode 100644 index 0000000000..fd03da61a3 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/package.json b/webapp/packages/plugin-resource-manager-administration/package.json new file mode 100644 index 0000000000..4a64eca9f9 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/package.json @@ -0,0 +1,45 @@ +{ + "name": "@cloudbeaver/plugin-resource-manager-administration", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "~0.1.0", + "@cloudbeaver/core-app": "~0.1.0", + "@cloudbeaver/core-authentication": "~0.1.0", + "@cloudbeaver/core-blocks": "~0.1.0", + "@cloudbeaver/core-connections": "~0.1.0", + "@cloudbeaver/core-di": "~0.1.0", + "@cloudbeaver/core-dialogs": "~0.1.0", + "@cloudbeaver/core-events": "~0.1.0", + "@cloudbeaver/core-executor": "~0.1.0", + "@cloudbeaver/core-extensions": "~0.1.0", + "@cloudbeaver/core-localization": "~0.1.0", + "@cloudbeaver/core-notifications": "~0.1.0", + "@cloudbeaver/core-plugin": "~0.1.0", + "@cloudbeaver/core-product": "~0.1.0", + "@cloudbeaver/core-root": "~0.1.0", + "@cloudbeaver/core-routing": "~0.1.0", + "@cloudbeaver/core-sdk": "~0.1.0", + "@cloudbeaver/core-settings": "~0.1.0", + "@cloudbeaver/core-theming": "~0.1.0", + "@cloudbeaver/core-ui": "~0.1.0", + "@cloudbeaver/core-utils": "~0.1.0", + "@cloudbeaver/core-view": "~0.1.0", + "react": "~17.x.x", + "mobx": "~6.x.x", + "mobx-react-lite": "~3.x.x", + "reshadow": "~0.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/LocaleService.ts b/webapp/packages/plugin-resource-manager-administration/src/LocaleService.ts new file mode 100644 index 0000000000..90c761184a --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/LocaleService.ts @@ -0,0 +1,36 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable() +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + register(): void | Promise { + this.localizationService.addProvider(this.provider.bind(this)); + } + + load(): void | Promise { } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru')).default; + case 'it': + return (await import('./locales/it')).default; + case 'zh': + return (await import('./locales/zh')).default; + default: + return (await import('./locales/en')).default; + } + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts new file mode 100644 index 0000000000..52449aeaf2 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/PluginBootstrap.ts @@ -0,0 +1,54 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { ExecutorInterrupter, IExecutionContextProvider } from '@cloudbeaver/core-executor'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { ILoadConfigData, ServerConfigurationService } from '@cloudbeaver/plugin-administration'; + +import { ResourceManagerSettings } from './ResourceManagerSettings'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly serverConfigurationService: ServerConfigurationService, + private readonly serverConfigResource: ServerConfigResource, + private readonly notificationService: NotificationService + ) { + super(); + + this.loadConfigHandler = this.loadConfigHandler.bind(this); + } + + register(): void | Promise { + this.serverConfigurationService.pluginsContainer.add(ResourceManagerSettings, 0); + this.serverConfigurationService.loadConfigTask.addHandler(this.loadConfigHandler); + } + + async load(): Promise { } + + private async loadConfigHandler(data: ILoadConfigData, contexts: IExecutionContextProvider) { + if (!data.reset) { + return; + } + + try { + const config = await this.serverConfigResource.load(); + + if (!config) { + return; + } + + data.state.serverConfig.resourceManagerEnabled = config.resourceManagerEnabled; + } catch (exception) { + ExecutorInterrupter.interrupt(contexts); + this.notificationService.logException(exception as any, 'Can\'t load server configuration'); + } + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/ResourceManagerSettings.tsx b/webapp/packages/plugin-resource-manager-administration/src/ResourceManagerSettings.tsx new file mode 100644 index 0000000000..8efa05619b --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/ResourceManagerSettings.tsx @@ -0,0 +1,35 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { BASE_CONTAINERS_STYLES, GroupTitle, PlaceholderComponent, Switch } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration'; + +export const ResourceManagerSettings: PlaceholderComponent = observer( + function ResourceManagerSettings({ state }) { + const translate = useTranslate(); + + return styled(BASE_CONTAINERS_STYLES)( + <> + {translate('plugin_resource_manager_title')} + + {translate('plugin_resource_manager_administration_settings_enable')} + + + ); + }); \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/index.ts b/webapp/packages/plugin-resource-manager-administration/src/index.ts new file mode 100644 index 0000000000..7a00070e27 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/index.ts @@ -0,0 +1,3 @@ +import { resourceManagerAdministrationPlugin } from './manifest'; + +export default resourceManagerAdministrationPlugin; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/locales/en.ts b/webapp/packages/plugin-resource-manager-administration/src/locales/en.ts new file mode 100644 index 0000000000..800db43248 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/locales/en.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_resource_manager_administration_settings_enable', 'Enable Resource manager'], + ['plugin_resource_manager_administration_settings_enable_description', 'Enable Resource manager functionality'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/locales/it.ts b/webapp/packages/plugin-resource-manager-administration/src/locales/it.ts new file mode 100644 index 0000000000..800db43248 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/locales/it.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_resource_manager_administration_settings_enable', 'Enable Resource manager'], + ['plugin_resource_manager_administration_settings_enable_description', 'Enable Resource manager functionality'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/locales/ru.ts b/webapp/packages/plugin-resource-manager-administration/src/locales/ru.ts new file mode 100644 index 0000000000..2a93554670 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/locales/ru.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_resource_manager_administration_settings_enable', 'Включить Менеджер ресурсов'], + ['plugin_resource_manager_administration_settings_enable_description', 'Включает функционал Менеджера ресурсов'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/locales/zh.ts b/webapp/packages/plugin-resource-manager-administration/src/locales/zh.ts new file mode 100644 index 0000000000..800db43248 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/locales/zh.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_resource_manager_administration_settings_enable', 'Enable Resource manager'], + ['plugin_resource_manager_administration_settings_enable_description', 'Enable Resource manager functionality'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/src/manifest.ts b/webapp/packages/plugin-resource-manager-administration/src/manifest.ts new file mode 100644 index 0000000000..9729a95623 --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/src/manifest.ts @@ -0,0 +1,20 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import type { PluginManifest } from '@cloudbeaver/core-di'; + +import { LocaleService } from './LocaleService'; +import { PluginBootstrap } from './PluginBootstrap'; + +export const resourceManagerAdministrationPlugin: PluginManifest = { + info: { name: 'Resource manager administration plugin' }, + providers: [ + PluginBootstrap, + LocaleService, + ], +}; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager-administration/tsconfig.json b/webapp/packages/plugin-resource-manager-administration/tsconfig.json new file mode 100644 index 0000000000..bca25a8e0a --- /dev/null +++ b/webapp/packages/plugin-resource-manager-administration/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "composite": true + }, + "include": [ + "src" + ], + "exclude": [ + "**/node_modules", + "**/.*/" + ], + "references": [ + { + "path": "../*" + } + ] +} \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager/src/ResourceManagerService.ts b/webapp/packages/plugin-resource-manager/src/ResourceManagerService.ts index 7870cd264a..69bdf2a5f2 100644 --- a/webapp/packages/plugin-resource-manager/src/ResourceManagerService.ts +++ b/webapp/packages/plugin-resource-manager/src/ResourceManagerService.ts @@ -10,20 +10,19 @@ import { computed, makeObservable, observable } from 'mobx'; import { AuthInfoService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; - -import { ResourceManagerSettingsService } from './ResourceManagerSettingsService'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; @injectable() export class ResourceManagerService { get enabled() { - return !this.resourceManagerSettingsService.settings.getValue('disabled') && !!this.authInfoService.userInfo; + return this.serverConfigResource.resourceManagerEnabled && !!this.authInfoService.userInfo; } panelEnabled: boolean; constructor( - private readonly resourceManagerSettingsService: ResourceManagerSettingsService, private readonly authInfoService: AuthInfoService, + private readonly serverConfigResource: ServerConfigResource ) { this.togglePanel = this.togglePanel.bind(this); diff --git a/webapp/packages/plugin-resource-manager/src/SaveScriptDialog.tsx b/webapp/packages/plugin-resource-manager/src/SaveScriptDialog.tsx index a8ce5b4f42..ad3ae4b5d5 100644 --- a/webapp/packages/plugin-resource-manager/src/SaveScriptDialog.tsx +++ b/webapp/packages/plugin-resource-manager/src/SaveScriptDialog.tsx @@ -32,7 +32,7 @@ interface State { submit: () => Promise; } -const regex = /^(?!\.)[\p{L}\w\-$.\s]+$/u; +const regex = /^(?!\.)[\p{L}\w\-$.\s()]+$/u; export const SaveScriptDialog: DialogComponent = observer(function SaveScriptDialog({ payload, diff --git a/webapp/packages/plugin-resource-manager/src/locales/en.ts b/webapp/packages/plugin-resource-manager/src/locales/en.ts index 2de54ee0fd..129d4cb651 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/en.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/en.ts @@ -14,5 +14,5 @@ export default [ ['plugin_resource_manager_sync_script_error', "Failed to sync editor's query"], ['plugin_resource_manager_script_not_found_title', 'Failed to find the associated script'], ['plugin_resource_manager_script_not_found_message', 'The script was probably removed or renamed. The last known value will be used'], - ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$." and can\'t start with a dot'], + ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$.()" and can\'t start with a dot'], ]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager/src/locales/it.ts b/webapp/packages/plugin-resource-manager/src/locales/it.ts index 4cc87825d4..3ba86c11c0 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/it.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/it.ts @@ -14,5 +14,5 @@ export default [ ['plugin_resource_manager_sync_script_error', "Failed to sync editor's query"], ['plugin_resource_manager_script_not_found_title', 'Failed to find the associated script'], ['plugin_resource_manager_script_not_found_message', 'The script was probably removed or renamed. The last known value will be used'], - ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$." and can\'t start with a dot'], + ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$.()" and can\'t start with a dot'], ]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager/src/locales/ru.ts b/webapp/packages/plugin-resource-manager/src/locales/ru.ts index bf320f8591..84e9d2a8a7 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/ru.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/ru.ts @@ -14,5 +14,5 @@ export default [ ['plugin_resource_manager_sync_script_error', 'Не удалось синхронизировать данные редактора'], ['plugin_resource_manager_script_not_found_title', 'Не удалось найти привязанный скрипт'], ['plugin_resource_manager_script_not_found_message', 'Возможно скрипт был удален или переименован. Будет использовано последнее известное значение'], - ['plugin_resource_manager_script_name_invalid_characters_message', 'Имя скрипта может содержать следующие символы "_-$." и не может начинаться с точки'], + ['plugin_resource_manager_script_name_invalid_characters_message', 'Имя скрипта может содержать следующие символы "_-$.()" и не может начинаться с точки'], ]; \ No newline at end of file diff --git a/webapp/packages/plugin-resource-manager/src/locales/zh.ts b/webapp/packages/plugin-resource-manager/src/locales/zh.ts index 3697c691e4..640d9cab22 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/zh.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/zh.ts @@ -14,5 +14,5 @@ export default [ ['plugin_resource_manager_sync_script_error', '同步查询失败'], ['plugin_resource_manager_script_not_found_title', '找不到关联的脚本'], ['plugin_resource_manager_script_not_found_message', '脚本可能被删除或重命名,将使用最后的有效值'], - ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$." and can\'t start with a dot'], + ['plugin_resource_manager_script_name_invalid_characters_message', 'Script\'s name may contain the following symbols "_-$.()" and can\'t start with a dot'], ]; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts index 3b7135d9d2..4d08df8004 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/PluginBootstrap.ts @@ -14,8 +14,8 @@ import { SessionExpireService } from '@cloudbeaver/core-root'; import { createPath } from '@cloudbeaver/core-utils'; import { ActionService, ACTION_SAVE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; import { NavResourceNodeService, RESOURCE_NODE_TYPE, SaveScriptDialog, ResourceManagerService, ProjectsResource, RESOURCES_NODE_PATH } from '@cloudbeaver/plugin-resource-manager'; -import { DATA_CONTEXT_SQL_EDITOR_STATE, SqlEditorService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor'; -import { SqlEditorNavigatorService, SqlEditorTabService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; +import { DATA_CONTEXT_SQL_EDITOR_STATE, getSqlEditorName, SqlEditorService, SQL_EDITOR_ACTIONS_MENU } from '@cloudbeaver/plugin-sql-editor'; +import { SqlEditorNavigatorService } from '@cloudbeaver/plugin-sql-editor-navigation-tab'; import { isScript } from './isScript'; import { SCRIPT_EXTENSION } from './SCRIPT_EXTENSION'; @@ -27,7 +27,6 @@ export class PluginBootstrap extends Bootstrap { private readonly navNodeManagerService: NavNodeManagerService, private readonly navTreeResource: NavTreeResource, private readonly navResourceNodeService: NavResourceNodeService, - private readonly sqlEditorTabService: SqlEditorTabService, private readonly sqlEditorService: SqlEditorService, private readonly navNodeInfoResource: NavNodeInfoResource, private readonly navigationTabsService: NavigationTabsService, @@ -64,9 +63,9 @@ export class PluginBootstrap extends Bootstrap { const tab = this.navigationTabsService.currentTab; if (action === ACTION_SAVE) { - const tabName = this.sqlEditorTabService.getName(state); + const name = getSqlEditorName(state); const result = await this.commonDialogService.open(SaveScriptDialog, { - defaultScriptName: tabName, + defaultScriptName: name, }); if (result !== DialogueStateResult.Rejected && result !== DialogueStateResult.Resolved) { diff --git a/webapp/packages/product-default/src/index.ts b/webapp/packages/product-default/src/index.ts index b365bc4749..bee503523c 100644 --- a/webapp/packages/product-default/src/index.ts +++ b/webapp/packages/product-default/src/index.ts @@ -19,6 +19,7 @@ import localization from '@cloudbeaver/plugin-localization'; import objectViewer from '@cloudbeaver/plugin-object-viewer'; import productPlugin from '@cloudbeaver/plugin-product'; import resourceManager from '@cloudbeaver/plugin-resource-manager'; +import resourceManagerAdministration from '@cloudbeaver/plugin-resource-manager-administration'; import settingsMenu from '@cloudbeaver/plugin-settings-menu'; import settingsMenuAdministration from '@cloudbeaver/plugin-settings-menu-administration'; import sqlEditor from '@cloudbeaver/plugin-sql-editor'; @@ -60,6 +61,7 @@ const PLUGINS: PluginManifest[] = [ settingsMenuAdministration, help, resourceManager, + resourceManagerAdministration, sqlEditorResource, ];