From 6283871ca260ac90ae75d16de479bbbb046bb0c4 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 17 Jun 2021 14:52:34 +0300 Subject: [PATCH 01/15] CB-1061 SAML configuration and servlet --- .../src/io/cloudbeaver/server/CBApplication.java | 11 +++++++++++ .../src/io/cloudbeaver/server/CBConstants.java | 2 ++ 2 files changed, 13 insertions(+) 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 279c036d15..8860f4fdec 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 @@ -74,6 +74,7 @@ public class CBApplication extends BaseApplicationImpl { return (CBApplication) BaseApplicationImpl.getInstance(); } + private String serverURL; private int serverPort = CBConstants.DEFAULT_SERVER_PORT; private String serverName = null; private String contentRoot = CBConstants.DEFAULT_CONTENT_ROOT; @@ -103,6 +104,10 @@ public class CBApplication extends BaseApplicationImpl { public CBApplication() { } + public String getServerURL() { + return serverURL; + } + public int getServerPort() { return serverPort; } @@ -403,6 +408,12 @@ public class CBApplication extends BaseApplicationImpl { Map serverConfig = JSONUtils.getObject(configProps, "server"); serverPort = JSONUtils.getInteger(serverConfig, CBConstants.PARAM_SERVER_PORT, serverPort); + if (serverConfig.containsKey(CBConstants.PARAM_SERVER_URL)) { + serverURL = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_URL, serverURL); + } else if (serverURL == null) { + serverURL = "http://" + InetAddress.getLocalHost().getHostName() + ":" + serverPort; + } + serverName = JSONUtils.getString(serverConfig, CBConstants.PARAM_SERVER_NAME, serverName); contentRoot = getRelativePath( JSONUtils.getString(serverConfig, CBConstants.PARAM_CONTENT_ROOT, contentRoot), homeFolder); 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 fbae82e57c..1c2599b0aa 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 @@ -38,6 +38,7 @@ public class CBConstants { public static final String PARAM_SERVER_PORT = "serverPort"; public static final String PARAM_SERVER_NAME = "serverName"; public static final String PARAM_CONTENT_ROOT = "contentRoot"; + public static final String PARAM_SERVER_URL = "serverURL"; public static final String PARAM_ROOT_URI = "rootURI"; public static final String PARAM_SERVICES_URI = "serviceURI"; public static final String PARAM_DRIVERS_LOCATION = "driversLocation"; @@ -69,6 +70,7 @@ public class CBConstants { public static final String VAR_CB_LOCAL_HOST_ADDR = "CB_LOCAL_HOST_ADDR"; + public static final String DEFAULT_SERVER_ADDR = "localhost"; public static final String VAR_HOST_DOCKER_INTERNAL = "host.docker.internal"; From 45c4d3e653f4fda50461f78a39a02011fbef7cf7 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Fri, 18 Jun 2021 11:54:09 +0300 Subject: [PATCH 02/15] CB-1061 Server URL configuration parameter --- .../io.cloudbeaver.server/schema/service.core.graphqls | 2 ++ .../src/io/cloudbeaver/model/WebServerConfig.java | 5 +++++ .../src/io/cloudbeaver/server/CBApplication.java | 10 +++++++--- .../schema/service.admin.graphqls | 1 + .../cloudbeaver/service/admin/AdminServerConfig.java | 10 ++++++++++ .../service/admin/impl/WebServiceAdmin.java | 1 + 6 files changed, 26 insertions(+), 3 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 5425bb9bca..04f0bc0b15 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -90,6 +90,8 @@ type ServerConfig { version: String! workspaceId: ID! + serverURL: String! + anonymousAccessEnabled: Boolean authenticationEnabled: Boolean @deprecated supportsCustomConnections: Boolean diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java index b65aae1202..611b731d85 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerConfig.java @@ -59,6 +59,11 @@ public class WebServerConfig { return DBWorkbench.getPlatform().getWorkspace().getWorkspaceId(); } + @Property + public String getServerURL() { + return CommonUtils.notEmpty(application.getServerURL()); + } + @Property public boolean isAnonymousAccessEnabled() { return application.getAppConfiguration().isAnonymousAccessEnabled(); 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 8860f4fdec..cf1d960f2a 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 @@ -533,6 +533,7 @@ public class CBApplication extends BaseApplicationImpl { public synchronized void finishConfiguration( @NotNull String newServerName, + @NotNull String newServerURL, @NotNull String adminName, @Nullable String adminPassword, @NotNull List authInfoList, @@ -549,7 +550,7 @@ public class CBApplication extends BaseApplicationImpl { // Save runtime configuration log.debug("Saving runtime configuration"); - saveRuntimeConfig(newServerName, sessionExpireTime, appConfig); + saveRuntimeConfig(newServerName, newServerURL, sessionExpireTime, appConfig); // Grant permissions to predefined connections if (isConfigurationMode() && appConfig.isAnonymousAccessEnabled()) { @@ -572,7 +573,7 @@ public class CBApplication extends BaseApplicationImpl { } public synchronized void flushConfiguration() throws DBException { - saveRuntimeConfig(serverName, maxSessionIdleTime, appConfiguration); + saveRuntimeConfig(serverName, serverURL, maxSessionIdleTime, appConfiguration); } @@ -594,7 +595,7 @@ public class CBApplication extends BaseApplicationImpl { } } - private void saveRuntimeConfig(String newServerName, long sessionExpireTime, CBAppConfig appConfig) throws DBException { + private void saveRuntimeConfig(String newServerName, String newServerURL, long sessionExpireTime, CBAppConfig appConfig) throws DBException { File runtimeConfigFile = getRuntimeAppConfigFile(); try (Writer out = new OutputStreamWriter(new FileOutputStream(runtimeConfigFile), StandardCharsets.UTF_8)) { @@ -611,6 +612,9 @@ public class CBApplication extends BaseApplicationImpl { if (!CommonUtils.isEmpty(newServerName)) { JSONUtils.field(json, CBConstants.PARAM_SERVER_NAME, newServerName); } + if (!CommonUtils.isEmpty(newServerURL)) { + JSONUtils.field(json, CBConstants.PARAM_SERVER_URL, newServerURL); + } if (sessionExpireTime > 0) { JSONUtils.field(json, CBConstants.PARAM_SESSION_EXPIRE_PERIOD, sessionExpireTime); } diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index 46b706fe95..468655d8a0 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -50,6 +50,7 @@ type AdminPermissionInfo { input ServerConfigInput { serverName: String + serverURL: String adminName: String adminPassword: String diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java index db231e48ce..8a4888727a 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminServerConfig.java @@ -30,6 +30,7 @@ import java.util.Map; public class AdminServerConfig { private String serverName; + private String serverURL; private String adminName; private String adminPassword; @@ -44,6 +45,7 @@ public class AdminServerConfig { public AdminServerConfig(Map params) { this.serverName = JSONUtils.getString(params, "serverName"); + this.serverURL = JSONUtils.getString(params, "serverURL"); this.adminName = JSONUtils.getString(params, "adminName"); this.adminPassword = JSONUtils.getString(params, "adminPassword"); @@ -69,6 +71,14 @@ public class AdminServerConfig { this.serverName = serverName; } + public String getServerURL() { + return serverURL; + } + + public void setServerURL(String serverURL) { + this.serverURL = serverURL; + } + public String getAdminName() { return adminName; } diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java index f2aa4a383e..1f151c7dcf 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java @@ -419,6 +419,7 @@ public class WebServiceAdmin implements DBWServiceAdmin { CBApplication.getInstance().finishConfiguration( config.getServerName(), + config.getServerURL(), adminName, adminPassword, authInfoList, From e4a62ba7f5b8c7cc9be717a254287957a44ef5d8 Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 18 Jun 2021 14:39:42 +0300 Subject: [PATCH 03/15] fix(plugin-object-viewer): CB-1029 virtual tabs --- .../ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTab.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTab.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTab.tsx index 75b06275e0..f5605e074b 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTab.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/VirtualFolderTab/VirtualFolderTab.tsx @@ -18,7 +18,6 @@ import { VirtualFolderTabMixin } from './VirtualFolderTabMixin'; const style = css` tab-wrapper { position: relative; - display: flex; width: 100%; flex: 1 0 auto; } @@ -32,7 +31,7 @@ export const VirtualFolderTab = observer(function VirtualFolderTab() { } return styled(style)( - + ); From 5be0c0a7b17f792bf3788962dfeccf21636d033d Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 18 Jun 2021 15:38:14 +0300 Subject: [PATCH 04/15] feat(core-localization): hot language change --- .../src/LocalizationService.ts | 40 ++++++++++--------- .../packages/core-root/src/SessionResource.ts | 16 ++++++++ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/webapp/packages/core-localization/src/LocalizationService.ts b/webapp/packages/core-localization/src/LocalizationService.ts index 0f283a5b2f..1f8287b8a9 100644 --- a/webapp/packages/core-localization/src/LocalizationService.ts +++ b/webapp/packages/core-localization/src/LocalizationService.ts @@ -10,8 +10,8 @@ import { action, observable, makeObservable } from 'mobx'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { SessionService, ServerService } from '@cloudbeaver/core-root'; -import { GraphQLService, ServerLanguage } from '@cloudbeaver/core-sdk'; +import { ServerService, SessionResource, SessionDataResource } from '@cloudbeaver/core-root'; +import type { ServerLanguage } from '@cloudbeaver/core-sdk'; import { SettingsService } from '@cloudbeaver/core-settings'; import type { ILocaleProvider } from './ILocaleProvider'; @@ -37,9 +37,9 @@ export class LocalizationService extends Bootstrap { constructor( private notificationService: NotificationService, - private sessionService: SessionService, + private sessionResource: SessionResource, + sessionDataResource: SessionDataResource, private serverService: ServerService, - private graphQLService: GraphQLService, private settingsService: SettingsService ) { super(); @@ -49,6 +49,8 @@ export class LocalizationService extends Bootstrap { localeMap: observable.shallow, setCurrentLocale: action, }); + + sessionDataResource.onDataUpdate.addHandler(this.syncLanguage.bind(this)); } addProvider(provider: ILocaleProvider): void { @@ -78,20 +80,12 @@ export class LocalizationService extends Bootstrap { register(): void | Promise { this.addProvider(this.coreProvider.bind(this)); + this.settingsService.registerSettings(this.settings, LANG_SETTINGS_KEY); // overwrite default value with settings } async load(): Promise { - const session = await this.sessionService.session.load(); await this.loadLocaleAsync(DEFAULT_LOCALE_NAME); - - if (!session) { - return; - } - - this.settingsService.registerSettings(this.settings, LANG_SETTINGS_KEY); // overwrite default value with settings - this.setCurrentLocale(session.locale); // session language wins - - await this.setLocale(this.getCurrentLanguage()); + await this.loadLocaleAsync(this.settings.language); } getCurrentLanguage(): string { @@ -102,10 +96,16 @@ export class LocalizationService extends Bootstrap { if (key === this.settings.language) { return; } - const response = await this.graphQLService.sdk.changeSessionLanguage({ locale: key }); - this.setLocale(key); - if (response.changeSessionLanguage) { - window.location.reload(); + await this.sessionResource.changeLanguage(key); + // window.location.reload(); + } + + private async syncLanguage() { + await this.sessionResource.refreshSilent(); // TODO: remove + const session = this.sessionResource.data; + + if (session) { + await this.setLocale(session.locale); } } @@ -126,13 +126,15 @@ export class LocalizationService extends Bootstrap { const config = await this.serverService.config.load(); if (!config) { - throw new Error('Cant\'t get server settings'); + throw new Error('Can\'t get server settings'); } if (!config.supportedLanguages.some(lang => lang.isoCode === key)) { this.setCurrentLocale(config!.supportedLanguages[0]!.isoCode); throw new Error(`Language '${key}' is not supported`); } + + this.setCurrentLocale(key); await this.loadLocaleAsync(key); } diff --git a/webapp/packages/core-root/src/SessionResource.ts b/webapp/packages/core-root/src/SessionResource.ts index 531e9d4f71..f39705152f 100644 --- a/webapp/packages/core-root/src/SessionResource.ts +++ b/webapp/packages/core-root/src/SessionResource.ts @@ -28,6 +28,22 @@ export class SessionResource extends CachedDataResource { + const { session } = await this.graphQLService.sdk.openSession(); + + this.data = session; + } + + async changeLanguage(locale: string): Promise { + await this.performUpdate(undefined, undefined, async () => { + await this.graphQLService.sdk.changeSessionLanguage({ locale }); + + if (this.data) { + this.data.locale = locale; + } + }); + } + protected async loader(): Promise { const { session } = await this.graphQLService.sdk.openSession(); From f1c3595b6b8abb123d4648b25bd0a5dbec2df6e5 Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 18 Jun 2021 15:39:09 +0300 Subject: [PATCH 05/15] fix(core-app): auto connect to expanded connection on refresh --- .../NavigationTree/NavigationTreeService.ts | 21 +++++++++++++++---- .../src/NavigationTree/useElementsTree.ts | 4 ++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts index f3cb2640cc..3674ce5d3e 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts @@ -8,7 +8,7 @@ import { action, makeObservable } from 'mobx'; -import { ConnectionAuthService } from '@cloudbeaver/core-connections'; +import { ConnectionAuthService, ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { Executor, IExecutor } from '@cloudbeaver/core-executor'; @@ -37,6 +37,7 @@ export class NavigationTreeService { private navNodeManagerService: NavNodeManagerService, private notificationService: NotificationService, private connectionAuthService: ConnectionAuthService, + private connectionInfoResource: ConnectionInfoResource, private navNodeExtensionsService: NavNodeExtensionsService, private navTreeResource: NavTreeResource ) { @@ -61,10 +62,22 @@ export class NavigationTreeService { await this.navNodeManagerService.navToNode(id, parentId); } - async loadNestedNodes(id = ROOT_NODE_PATH): Promise { + async loadNestedNodes(id = ROOT_NODE_PATH, tryConnect?: boolean): Promise { try { - if (this.isConnectionNode(id) && !(await this.tryInitConnection(id))) { - return false; + if (this.isConnectionNode(id)) { + const connection = await this.connectionInfoResource.load( + NodeManagerUtils.connectionNodeIdToConnectionId(id) + ); + + if (!connection.connected && !tryConnect) { + return false; + } + + const connected = await this.tryInitConnection(id); + + if (!connected) { + return false; + } } await this.navNodeManagerService.loadTree(id); return true; diff --git a/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts index 4556dd9717..89f1c344da 100644 --- a/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts +++ b/webapp/packages/core-app/src/NavigationTree/useElementsTree.ts @@ -88,7 +88,7 @@ export function useElementsTree(options: IOptions): IElementsTree { continue; } - const loaded = await navTreeService.loadNestedNodes(child); + const loaded = await navTreeService.loadNestedNodes(child, false); if (!loaded) { const node = navNodeInfoResource.get(child); @@ -205,7 +205,7 @@ export function useElementsTree(options: IOptions): IElementsTree { try { if (state) { - state = await navTreeService.loadNestedNodes(node.id); + state = await navTreeService.loadNestedNodes(node.id, true); } await options.onExpand?.(node, state); From d230548f91661ec2f055aa6cc446da12d087e991 Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 18 Jun 2021 15:49:16 +0300 Subject: [PATCH 06/15] fix: CB-728 properly close menu after click --- .../TreeNodeMenu/TreeNodeMenu.tsx | 2 +- .../src/TopNavBar/MainMenu/MainMenu.tsx | 2 +- .../TopNavBar/SettingsMenu/SettingsMenu.tsx | 2 +- .../SettingsMenu/settingsMenuStyles.ts | 2 - .../src/TopNavBar/shared/TopMenuItem.tsx | 8 ++-- .../src/TopNavBar/shared/topMenuStyles.ts | 8 ++-- webapp/packages/core-blocks/src/useMouse.ts | 8 +++- .../src/Menu/ContextMenu/ContextMenu.ts | 2 + .../core-dialogs/src/Menu/IMenuPanel.ts | 2 + .../src/Menu/MenuPanel/MenuPanelItem.tsx | 8 ++-- .../src/Menu/MenuPanel/MenuTrigger.tsx | 47 +++++++++++++------ .../src/Menu/MenuPanel/menuPanelStyles.ts | 11 +++-- .../src/Menu/models/ComputedMenuItemModel.ts | 2 + .../src/Menu/models/MenuOptionsStore.ts | 1 + .../plugin-authentication/src/UserInfo.tsx | 2 +- .../src/DataGrid/DataGridLoader.tsx | 6 +-- .../src/DataGrid/Formatters/Menu/CellMenu.tsx | 1 + .../TableFooterMenu/TableFooterMenuItem.tsx | 1 + 18 files changed, 73 insertions(+), 42 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu.tsx index 03eb1f53ab..cea859bdf1 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu.tsx @@ -45,7 +45,7 @@ export const TreeNodeMenu = observer(function TreeNodeMenu({ } return styled(treeNodeMenuStyles)( - + ); diff --git a/webapp/packages/core-app/src/TopNavBar/MainMenu/MainMenu.tsx b/webapp/packages/core-app/src/TopNavBar/MainMenu/MainMenu.tsx index 45da064f64..e8ce659d61 100644 --- a/webapp/packages/core-app/src/TopNavBar/MainMenu/MainMenu.tsx +++ b/webapp/packages/core-app/src/TopNavBar/MainMenu/MainMenu.tsx @@ -31,7 +31,7 @@ export const MainMenu = observer(function MainMenu() { const mainMenuService = useService(MainMenuService); return styled(styles)( - + {mainMenuService.getMainMenu().map((topItem, i) => ( ))} diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx index f13b3ba9da..ff4b750637 100644 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx +++ b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx @@ -22,7 +22,7 @@ export const SettingsMenu = observer(function SettingsMenu() { const settingsMenuService = useService(SettingsMenuService); return styled(useStyles(settingsMenuStyles))( - + ); diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts index b5bf2450d2..d986965784 100644 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts +++ b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts @@ -26,10 +26,8 @@ export const settingsMenuStyles = css` height: 24px; } menu-panel-item { - flex-direction: row-reverse; & menu-item-text { text-align: right; - margin-left: auto; } & Icon { transform: rotate(90deg); diff --git a/webapp/packages/core-app/src/TopNavBar/shared/TopMenuItem.tsx b/webapp/packages/core-app/src/TopNavBar/shared/TopMenuItem.tsx index 7200b5d3a0..adc93ce70b 100644 --- a/webapp/packages/core-app/src/TopNavBar/shared/TopMenuItem.tsx +++ b/webapp/packages/core-app/src/TopNavBar/shared/TopMenuItem.tsx @@ -25,7 +25,8 @@ type TopMenuItemProps = Omit, 'style'> & { export const TopMenuItem = observer(function TopMenuItem({ menuItem, style = [], ...props }: TopMenuItemProps) { const translate = useTranslate(); - const styles = useStyles(!menuItem.panel ? [] : [...style, topMenuStyles]); + style = [...style, topMenuStyles]; + const styles = useStyles(!menuItem.panel ? [] : style); if (!menuItem.panel) { return ( @@ -44,12 +45,13 @@ export const TopMenuItem = observer(function TopMenuItem({ menuItem, style = [], {...props} panel={menuItem.panel} disabled={menuItem.isDisabled} - style={[...style, topMenuStyles]} + style={style} placement="bottom-end" + modal onClick={() => menuItem.onClick?.()} > {menuItem.icon && ( - + )} diff --git a/webapp/packages/core-app/src/TopNavBar/shared/topMenuStyles.ts b/webapp/packages/core-app/src/TopNavBar/shared/topMenuStyles.ts index 8c45278b29..d835506b17 100644 --- a/webapp/packages/core-app/src/TopNavBar/shared/topMenuStyles.ts +++ b/webapp/packages/core-app/src/TopNavBar/shared/topMenuStyles.ts @@ -12,7 +12,7 @@ import { composes } from '@cloudbeaver/core-theming'; export const topMenuStyles = composes( css` - Menu { + menu-box { composes: theme-text-on-primary from global; } @@ -21,7 +21,7 @@ export const topMenuStyles = composes( } `, css` - Menu { + menu-box { background-color: #338ecc!important; } MenuTrigger { @@ -55,7 +55,9 @@ export const topMenuStyles = composes( background: #236ea0; } } - MenuItem { + MenuItem, + MenuItemCheckbox, + MenuItemRadio { &:hover, &:global([aria-expanded="true"]) { background: #236ea0; } diff --git a/webapp/packages/core-blocks/src/useMouse.ts b/webapp/packages/core-blocks/src/useMouse.ts index bc4e6ad046..786da39604 100644 --- a/webapp/packages/core-blocks/src/useMouse.ts +++ b/webapp/packages/core-blocks/src/useMouse.ts @@ -41,7 +41,9 @@ export function useMouse(options: IOptions): IMouseHook { @@ -49,7 +51,9 @@ export function useMouse(options: IOptions): IMouseHook implements IComputedMenuItemOptions isChecked?: () => boolean; type?: MenuItemType; separator?: boolean; + keepMenuOpen?: boolean; iconGetter?: () => string | undefined; constructor(private options: IContextMenuItem, @@ -112,6 +113,7 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions this.icon = options.icon; this.type = options.type; this.separator = options.separator; + this.keepMenuOpen = options.keepMenuOpen; this.iconGetter = options.iconGetter; this.id = `${options.id}-${context.contextId!}`; diff --git a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts index dfff022226..77b0c90969 100644 --- a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts +++ b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts @@ -28,6 +28,7 @@ export interface IMenuItem { onClick?: () => void; // it is not mandatory if it is just opens submenu isDisabled?: boolean; isHidden?: boolean; + keepMenuOpen?: boolean; icon?: string; // path to icon or svg icon name tooltip?: string; panel?: IMenuPanel; // if menu has sub-items @@ -42,5 +43,6 @@ export type MenuTriggerProps = PropsWithChildren<{ placement?: MenuInitialState['placement']; modal?: boolean; visible?: boolean; + rtl?: boolean; onVisibleSwitch?: (visible: boolean) => void; }> & Omit, 'style'>; diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx index 747ef69949..27dc5de330 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx @@ -44,16 +44,16 @@ export const MenuPanelItem: React.FC = function MenuPanelIte } return styled(useStyles(menuPanelStyles, style))( - - + + {icon ? ( ) : control} - + - + {panel && } diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx index c905d693f2..a0ab6ed8ee 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx @@ -36,10 +36,15 @@ export const MenuTrigger: React.FC = function MenuTrigger({ visible, onVisibleSwitch, modal, + rtl, ...props }) { const propsRef = useObjectRef({ onVisibleSwitch, visible }, { onVisibleSwitch }); - const menu = useMenuState({ modal, placement, visible }); + const menu = useMenuState({ modal, placement, visible, rtl }); + + const handleItemClose = useCallback(() => { + menu.hide(); + }, [menu.hide]); useEffect(() => { propsRef.onVisibleSwitch?.(menu.visible); @@ -48,11 +53,9 @@ export const MenuTrigger: React.FC = function MenuTrigger({ return styled(useStyles(menuPanelStyles, ...style))( <> - - {children} - + {children} - + ); }; @@ -64,12 +67,16 @@ export const MenuTrigger: React.FC = function MenuTrigger({ interface MenuPanelProps { panel: IMenuPanel; menu: MenuStateReturn; // from reakit useMenuState + onItemClose?: () => void; + rtl?: boolean; style?: Style[]; } const MenuPanel = observer(function MenuPanel({ panel, menu, + rtl, + onItemClose, style = [], }: MenuPanelProps) { const styles = useStyles(menuPanelStyles, ...style); @@ -79,10 +86,12 @@ const MenuPanel = observer(function MenuPanel({ } return styled(styles)( - - {panel.menuItems.map(item => ( - - ))} + + + {panel.menuItems.map(item => ( + + ))} + ); }); @@ -94,21 +103,22 @@ const MenuPanel = observer(function MenuPanel({ type MenuPanelElementProps = Omit, 'style'> & { item: IMenuItem; menu: MenuStateReturn; // from reakit useMenuState + onItemClose?: () => void; style?: Style[]; }; const MenuPanelElement = observer(function MenuPanelElement({ - item, menu, style = [], + item, menu, onItemClose, style = [], }: MenuPanelElementProps) { const styles = useStyles(menuPanelStyles, ...style); const onClick = useCallback(() => { if (item.onClick) { item.onClick(); } - if (!item.panel) { - menu.hide(); + if (!item.keepMenuOpen && !item.panel) { + onItemClose?.(); } - }, [item, menu]); + }, [item, menu, onItemClose]); if (item.panel) { return styled(styles)( @@ -119,6 +129,7 @@ const MenuPanelElement = observer(function MenuPanelElement({ disabled={item.isDisabled} menuItem={item} style={style} + onItemClose={onItemClose} onClick={onClick} {...{ as: MenuInnerTrigger }} /> @@ -180,6 +191,7 @@ const MenuPanelElement = observer(function MenuPanelElement({ type MenuInnerTriggerProps = Omit, 'style'> & { menuItem: IMenuItem; + onItemClose?: () => void; style?: Style[]; }; @@ -193,14 +205,19 @@ export const MenuInnerTrigger = forwardRef(function MenuInnerTrigger( const menu = useMenuState(); const panel = useObserver(() => menuItem.panel); + const handleItemClose = useCallback(() => { + menu.hide(); + props.onItemClose?.(); + }, [menu.hide, props.onItemClose]); + return styled(useStyles(menuPanelStyles, ...style))( <> - + - + ); }); diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts index 528b96341c..68323f466e 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts @@ -12,7 +12,7 @@ import { composes } from '@cloudbeaver/core-theming'; export const menuPanelStyles = composes( css` - Menu { + menu-box { composes: theme-background-surface theme-text-on-surface from global; } MenuItem, @@ -43,16 +43,17 @@ export const menuPanelStyles = composes( } } Menu { + outline: none; + z-index: 999; + } + menu-box { composes: theme-typography--body2 theme-elevation-z5 from global; - position: absolute; display: flex; min-width: 140px; flex-direction: column; padding: 12px 0; - outline: none; - z-index: 999; - & Menu { + & menu-box { margin-top: -12px; } } diff --git a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts index c71025337e..36f6b3ea32 100644 --- a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts +++ b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts @@ -26,6 +26,7 @@ export class ComputedMenuItemModel implements IMenuItem { panel?: IMenuPanel; type?: MenuItemType; separator?: boolean; + keepMenuOpen?: boolean; rtl?: boolean; get title(): TLocalizationToken { @@ -73,6 +74,7 @@ export class ComputedMenuItemModel implements IMenuItem { this.id = options.id; this.type = options.type; + this.keepMenuOpen = options.keepMenuOpen; this.separator = options.separator; this.rtl = options.rtl; this.panel = options.panel; diff --git a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts index 918ac4cf9d..1c93e008f8 100644 --- a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts +++ b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts @@ -26,6 +26,7 @@ export interface IMenuItemOptions { tooltipGetter?: () => TLocalizationToken | undefined; order?: number; isPanel?: boolean; + keepMenuOpen?: boolean; panel?: IMenuPanel; type?: MenuItemType; separator?: boolean; diff --git a/webapp/packages/plugin-authentication/src/UserInfo.tsx b/webapp/packages/plugin-authentication/src/UserInfo.tsx index ad16535002..fbcdd7ecf8 100644 --- a/webapp/packages/plugin-authentication/src/UserInfo.tsx +++ b/webapp/packages/plugin-authentication/src/UserInfo.tsx @@ -53,7 +53,7 @@ export const UserInfo = observer(function UserInfo() { } return styled(style)( - + diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridLoader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridLoader.tsx index 1706126dc0..bd96959ac1 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridLoader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridLoader.tsx @@ -6,8 +6,6 @@ * you may not use this file except in compliance with the License. */ -import { observer } from 'mobx-react-lite'; - import { ComplexLoader, createComplexLoader, Loader } from '@cloudbeaver/core-blocks'; import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; @@ -22,7 +20,7 @@ const loader = createComplexLoader(async function loader() { return { DataGridTable }; }); -export const DataGridLoader: React.FC = observer(function DataGridLoader({ +export const DataGridLoader: React.FC = function DataGridLoader({ tableModel, resultIndex, className, }) { return ( @@ -35,4 +33,4 @@ export const DataGridLoader: React.FC = observer(function DataGridLoader( )} ); -}); +}; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx index c608a0893e..902c142194 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/Menu/CellMenu.tsx @@ -58,6 +58,7 @@ export const CellMenu: React.FC = observer(function TreeNodeMenu({ dataGridContextMenuService.openMenu(model, resultIndex, row, column)} onVisibleSwitch={onStateSwitch} > diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuItem.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuItem.tsx index 24c67747ab..05c437daed 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuItem.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuItem.tsx @@ -86,6 +86,7 @@ export const TableFooterMenuItem = observer(function TableFooterMenuItem({ panel={menuItem.panel} disabled={menuItem.isDisabled} style={[tableFooterMenuStyles]} + modal > {menuItem.icon && ( From edd9bbe6521cfbc8445ecd48c45d46e9c286a68d Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 18 Jun 2021 16:08:22 +0300 Subject: [PATCH 07/15] feat: CB-1062 add server url --- .../src/queries/querySqlDialectInfo.gql | 1 + .../src/queries/session/serverConfig.gql | 1 + webapp/packages/core-sdk/src/sdk.ts | 64 ++++++++++++++----- .../Form/ServerConfigurationInfoForm.tsx | 11 ++++ .../ServerConfigurationService.ts | 6 ++ .../plugin-administration/src/locales/en.ts | 2 + 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/webapp/packages/core-sdk/src/queries/querySqlDialectInfo.gql b/webapp/packages/core-sdk/src/queries/querySqlDialectInfo.gql index 4c5dfc4be5..33d8e7c16b 100644 --- a/webapp/packages/core-sdk/src/queries/querySqlDialectInfo.gql +++ b/webapp/packages/core-sdk/src/queries/querySqlDialectInfo.gql @@ -12,5 +12,6 @@ query querySqlDialectInfo( $connectionId: ID! ) { catalogSeparator structSeparator scriptDelimiter + supportsExplainExecutionPlan } } diff --git a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql index 79657fe94d..036d348752 100644 --- a/webapp/packages/core-sdk/src/queries/session/serverConfig.gql +++ b/webapp/packages/core-sdk/src/queries/session/serverConfig.gql @@ -3,6 +3,7 @@ query serverConfig { name version workspaceId + serverURL productConfiguration supportsCustomConnections supportsConnectionBrowser diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index d55e29eca1..48581c9f42 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -283,6 +283,8 @@ export interface Mutation { asyncReadDataFromContainer: AsyncTaskInfo; asyncSqlExecuteQuery: AsyncTaskInfo; asyncSqlExecuteResults: SqlExecuteInfo; + asyncSqlExplainExecutionPlan: AsyncTaskInfo; + asyncSqlExplainExecutionPlanResult: SqlExecutionPlan; asyncTaskCancel?: Maybe; asyncTaskInfo: AsyncTaskInfo; /** @deprecated Field no longer supported */ @@ -332,6 +334,17 @@ export interface MutationAsyncSqlExecuteResultsArgs { taskId: Scalars['ID']; } +export interface MutationAsyncSqlExplainExecutionPlanArgs { + connectionId: Scalars['ID']; + contextId: Scalars['ID']; + query: Scalars['String']; + configuration: Scalars['Object']; +} + +export interface MutationAsyncSqlExplainExecutionPlanResultArgs { + taskId: Scalars['ID']; +} + export interface MutationAsyncTaskCancelArgs { id: Scalars['String']; } @@ -503,6 +516,7 @@ export interface ServerConfig { name: Scalars['String']; version: Scalars['String']; workspaceId: Scalars['ID']; + serverURL: Scalars['String']; anonymousAccessEnabled?: Maybe; /** @deprecated Field no longer supported */ authenticationEnabled?: Maybe; @@ -772,26 +786,27 @@ export interface DatabaseStructContainers { } export interface SqlDialectInfo { - name?: Maybe; - dataTypes?: Maybe>>; - functions?: Maybe>>; - reservedWords?: Maybe>>; - quoteStrings?: Maybe>>>>; - singleLineComments?: Maybe>>; - multiLineComments?: Maybe>>>>; + name: Scalars['String']; + dataTypes: Array>; + functions: Array>; + reservedWords: Array>; + quoteStrings: Array>>>; + singleLineComments: Array>; + multiLineComments: Array>>>; catalogSeparator?: Maybe; structSeparator?: Maybe; scriptDelimiter?: Maybe; + supportsExplainExecutionPlan: Scalars['Boolean']; } export interface SqlCompletionProposal { - displayString?: Maybe; - type?: Maybe; + displayString: Scalars['String']; + type: Scalars['String']; score?: Maybe; - replacementString?: Maybe; - replacementOffset?: Maybe; - replacementLength?: Maybe; - cursorPosition?: Maybe; + replacementString: Scalars['String']; + replacementOffset: Scalars['Int']; + replacementLength: Scalars['Int']; + cursorPosition: Scalars['Int']; icon?: Maybe; nodePath?: Maybe; } @@ -876,6 +891,22 @@ export interface DataTypeLogicalOperation { argumentCount?: Maybe; } +export interface SqlExecutionPlan { + query: Scalars['String']; + nodes: SqlExecutionPlanNode[]; +} + +export interface SqlExecutionPlanNode { + id: Scalars['ID']; + parentId?: Maybe; + kind: Scalars['String']; + name?: Maybe; + type: Scalars['String']; + condition?: Maybe; + description?: Maybe; + properties: ObjectPropertyInfo[]; +} + export enum AdminSubjectType { User = 'user', Role = 'role' @@ -922,6 +953,7 @@ export interface AdminPermissionInfo { export interface ServerConfigInput { serverName?: Maybe; + serverURL?: Maybe; adminName?: Maybe; adminPassword?: Maybe; anonymousAccessEnabled?: Maybe; @@ -1580,7 +1612,7 @@ export type QuerySqlDialectInfoQueryVariables = Exact<{ connectionId: Scalars['ID']; }>; -export interface QuerySqlDialectInfoQuery { dialect?: Maybe> } +export interface QuerySqlDialectInfoQuery { dialect?: Maybe> } export type ConfigureServerQueryVariables = Exact<{ configuration: ServerConfigInput; @@ -1615,7 +1647,7 @@ export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>; export interface ServerConfigQuery { serverConfig: ( - Pick + Pick & { supportedLanguages: Array>; defaultNavigatorSettings: AllNavigatorSettingsFragment; productInfo: Pick } ); } @@ -2520,6 +2552,7 @@ export const QuerySqlDialectInfoDocument = ` catalogSeparator structSeparator scriptDelimiter + supportsExplainExecutionPlan } } `; @@ -2561,6 +2594,7 @@ export const ServerConfigDocument = ` name version workspaceId + serverURL productConfiguration supportsCustomConnections supportsConnectionBrowser diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx index 0c54cf22d6..750926d2e8 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx @@ -37,6 +37,17 @@ export const ServerConfigurationInfoForm: React.FC = observer(function Se > {translate('administration_configuration_wizard_configuration_server_name')} + + {translate('administration_configuration_wizard_configuration_server_url')} + Date: Fri, 18 Jun 2021 16:35:34 +0300 Subject: [PATCH 08/15] CB-1058 SQL completion API fix --- .../bundles/io.cloudbeaver.server/schema/service.sql.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls index 790196f905..b5f4388af9 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls @@ -28,7 +28,7 @@ type SQLCompletionProposal { replacementString: String! replacementOffset: Int! replacementLength: Int! - cursorPosition: Int! + cursorPosition: Int icon: String nodePath: String From 46042e1ce69e4603da81690fe3c030f76fa79415 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Fri, 18 Jun 2021 20:11:19 +0300 Subject: [PATCH 09/15] CB-1061 SAML authorization --- .../src/io/cloudbeaver/DBWAuthProviderExternal.java | 11 +++++++++++ .../auth/provider/AbstractExternalAuthProvider.java | 10 ++++++++++ 2 files changed, 21 insertions(+) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java index 286726eb75..82b83e7725 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWAuthProviderExternal.java @@ -45,6 +45,17 @@ public interface DBWAuthProviderExternal extend @NotNull Map authParameters // Passed auth parameters (e.g. user name or password) ) throws DBException; + /** + * Returns new identifying credentials which can be used to find/create user in database + */ + @NotNull + Map authFederatedUser( + @NotNull DBRProgressMonitor monitor, + @NotNull Map providerConfig, // Auth provider configuration (e.g. 3rd party auth server address) + @NotNull String faProvider, + @NotNull Map faParameters // Passed federated auth parameters (e.g. assertion ID, session token, etc) + ) throws DBException; + /** * Validates that external user may be associated with local user * @param userCredentials credentials from authExternalUser diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/auth/provider/AbstractExternalAuthProvider.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/auth/provider/AbstractExternalAuthProvider.java index 606357882e..a4f089836a 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/auth/provider/AbstractExternalAuthProvider.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/auth/provider/AbstractExternalAuthProvider.java @@ -18,9 +18,14 @@ package io.cloudbeaver.auth.provider; import io.cloudbeaver.DBWAuthProviderExternal; import io.cloudbeaver.model.user.WebUser; +import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.access.DBASession; +import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.utils.CommonUtils; +import java.util.Map; + /** * Abstract external auth provider */ @@ -34,4 +39,9 @@ public abstract class AbstractExternalAuthProvider i } } + @NotNull + @Override + public Map authFederatedUser(@NotNull DBRProgressMonitor monitor, @NotNull Map providerConfig, @NotNull String faProvider, @NotNull Map faParameters) throws DBException { + throw new DBException("Federated authorization not supported"); + } } From 8b5baf4e9a25703dd66030791b089a1b329bdb6e Mon Sep 17 00:00:00 2001 From: serge-rider Date: Fri, 18 Jun 2021 23:33:59 +0300 Subject: [PATCH 10/15] CB-1061 SAML authorization --- .../io/cloudbeaver/server/CBAppConfig.java | 22 ++++++++++++++++--- .../io/cloudbeaver/server/CBApplication.java | 3 +++ 2 files changed, 22 insertions(+), 3 deletions(-) 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 649850ced5..2d972545c2 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 @@ -19,6 +19,7 @@ package io.cloudbeaver.server; import io.cloudbeaver.auth.provider.local.LocalAuthProvider; import io.cloudbeaver.registry.WebAuthProviderDescriptor; import io.cloudbeaver.registry.WebServiceRegistry; +import org.jkiss.code.NotNull; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.registry.DataSourceNavigatorSettings; @@ -46,6 +47,7 @@ public class CBAppConfig { private String[] enabledAuthProviders = null; private DataSourceNavigatorSettings defaultNavigatorSettings = DEFAULT_VIEW_SETTINGS; private Map plugins = new LinkedHashMap<>(); + private Map authConfiguration = new LinkedHashMap<>(); public boolean isAnonymousAccessEnabled() { return anonymousAccessEnabled; @@ -132,15 +134,16 @@ public class CBAppConfig { this.defaultNavigatorSettings = new DataSourceNavigatorSettings(defaultNavigatorSettings); } + @NotNull public Map getPlugins() { return plugins; } - public Map getPluginConfig(String pluginId) { + public Map getPluginConfig(@NotNull String pluginId) { return getPluginConfig(pluginId, false); } - public Map getPluginConfig(String pluginId, boolean create) { + public Map getPluginConfig(@NotNull String pluginId, boolean create) { Object config = plugins.get(pluginId); if (config instanceof Map) { return (Map) config; @@ -155,8 +158,21 @@ public class CBAppConfig { } } - public T getPluginOption(String pluginId, String option) { + public T getPluginOption(@NotNull String pluginId, @NotNull String option) { return (T)getPluginConfig(pluginId).get(option); } + public Map getAuthConfiguration() { + return authConfiguration; + } + + @NotNull + public Map getAuthConfiguration(@NotNull String providerId) { + Object apConfig = authConfiguration.get(providerId); + if (apConfig instanceof Map) { + return (Map) apConfig; + } + return Collections.emptyMap(); + } + } 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 cf1d960f2a..af94669d8a 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 @@ -658,6 +658,9 @@ public class CBApplication extends BaseApplicationImpl { if (!CommonUtils.isEmpty(appConfig.getPlugins())) { JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins()); } + if (!CommonUtils.isEmpty(appConfig.getAuthConfiguration())) { + JSONUtils.serializeProperties(json, "authConfiguration", appConfig.getAuthConfiguration()); + } json.endObject(); } From 104fa9db6a769c386679cc69c8a544be63300203 Mon Sep 17 00:00:00 2001 From: Wroud Date: Sat, 19 Jun 2021 09:32:07 +0300 Subject: [PATCH 11/15] fix(core-blocks): CB-984 incorrect button type --- webapp/packages/core-blocks/src/ToolsPanel/ToolsAction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/core-blocks/src/ToolsPanel/ToolsAction.tsx b/webapp/packages/core-blocks/src/ToolsPanel/ToolsAction.tsx index 2dc7767082..95d4600108 100644 --- a/webapp/packages/core-blocks/src/ToolsPanel/ToolsAction.tsx +++ b/webapp/packages/core-blocks/src/ToolsPanel/ToolsAction.tsx @@ -51,7 +51,7 @@ interface Props extends ButtonHTMLAttributes { export const ToolsAction: React.FC = function ToolsAction({ icon, viewBox, children, ...rest }) { return styled(useStyles(styles))( - From 4899ba73db65f98cb6309700842d5125846ee20c Mon Sep 17 00:00:00 2001 From: Wroud Date: Sat, 19 Jun 2021 10:07:43 +0300 Subject: [PATCH 12/15] refactor: redesign license page --- webapp/packages/core-blocks/src/Button.tsx | 2 +- .../src/Containers/BASE_CONTAINERS_STYLES.ts | 1 + .../core-blocks/src/Containers/Group.tsx | 24 ++++++++++++++++++- .../core-blocks/src/FormControls/Combobox.tsx | 2 +- .../src/FormControls/ComboboxNew.tsx | 2 +- .../core-blocks/src/{Icons => }/Icon.tsx | 0 .../packages/core-blocks/src/IconButton.tsx | 2 +- .../packages/core-blocks/src/IconOrImage.tsx | 2 +- .../packages/core-blocks/src/Icons/index.ts | 1 - .../src/PropertiesTable/PropertyItem.tsx | 2 +- .../core-blocks/src/Slide/SlideOverlay.tsx | 2 +- .../core-blocks/src/Table/TableItemExpand.tsx | 2 +- .../packages/core-blocks/src/Tabs/Tab/Tab.tsx | 2 +- webapp/packages/core-blocks/src/index.ts | 2 +- 14 files changed, 34 insertions(+), 12 deletions(-) rename webapp/packages/core-blocks/src/{Icons => }/Icon.tsx (100%) delete mode 100644 webapp/packages/core-blocks/src/Icons/index.ts diff --git a/webapp/packages/core-blocks/src/Button.tsx b/webapp/packages/core-blocks/src/Button.tsx index e0fc3e2e37..c615552084 100644 --- a/webapp/packages/core-blocks/src/Button.tsx +++ b/webapp/packages/core-blocks/src/Button.tsx @@ -13,7 +13,7 @@ import { use } from 'reshadow'; import { useStyles, composes, ComponentStyle } from '@cloudbeaver/core-theming'; -import { Icon } from './Icons'; +import { Icon } from './Icon'; import { Loader } from './Loader/Loader'; import { useObjectRef } from './useObjectRef'; diff --git a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts index 24eecfdc2e..98787268ca 100644 --- a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts +++ b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts @@ -24,6 +24,7 @@ export const BASE_CONTAINERS_STYLES = composes( display: flex; flex-direction: row; align-content: baseline; + position: relative; &[vertical] { flex-direction: column; diff --git a/webapp/packages/core-blocks/src/Containers/Group.tsx b/webapp/packages/core-blocks/src/Containers/Group.tsx index 1d0b2084bf..dcbdf1967e 100644 --- a/webapp/packages/core-blocks/src/Containers/Group.tsx +++ b/webapp/packages/core-blocks/src/Containers/Group.tsx @@ -7,7 +7,9 @@ */ import { forwardRef } from 'react'; +import styled, { css } from 'reshadow'; +import { Icon } from '../Icon'; import { filterContainerFakeProps } from './filterContainerFakeProps'; import type { IContainerProps } from './IContainerProps'; @@ -15,15 +17,35 @@ interface Props extends IContainerProps { form?: boolean; center?: boolean; box?: boolean; + onClose?: () => void; } +const style = css` + close { + width: 18px; + height: 18px; + cursor: pointer; + display: flex; + position: absolute; + right: 24px; + margin-right: 0 !important; + } +`; + export const Group = forwardRef>(function Group({ form, center, box, + children, + onClose, ...rest }, ref) { const divProps = filterContainerFakeProps(rest); - return
; + return styled(style)( +
+ {onClose && } + {children} +
+ ); }); diff --git a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx index 4266e07895..232ec9dfd9 100644 --- a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx @@ -20,8 +20,8 @@ import styled, { css, use } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; +import { Icon } from '../Icon'; import { IconButton } from '../IconButton'; -import { Icon } from '../Icons/Icon'; import { baseFormControlStyles } from './baseFormControlStyles'; import { FormContext } from './FormContext'; diff --git a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx index bd735691c6..2af38bdfe7 100644 --- a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx @@ -21,8 +21,8 @@ import styled, { css, use } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; +import { Icon } from '../Icon'; import { IconButton } from '../IconButton'; -import { Icon } from '../Icons/Icon'; import { baseFormControlStylesNew } from './baseFormControlStylesNew'; import { FormContext } from './FormContext'; diff --git a/webapp/packages/core-blocks/src/Icons/Icon.tsx b/webapp/packages/core-blocks/src/Icon.tsx similarity index 100% rename from webapp/packages/core-blocks/src/Icons/Icon.tsx rename to webapp/packages/core-blocks/src/Icon.tsx diff --git a/webapp/packages/core-blocks/src/IconButton.tsx b/webapp/packages/core-blocks/src/IconButton.tsx index d9453fce41..c867a49ebc 100644 --- a/webapp/packages/core-blocks/src/IconButton.tsx +++ b/webapp/packages/core-blocks/src/IconButton.tsx @@ -9,7 +9,7 @@ import { Button, ButtonProps } from 'reakit/Button'; import styled, { css } from 'reshadow'; -import { Icon } from './Icons/Icon'; +import { Icon } from './Icon'; const styles = css` Button { diff --git a/webapp/packages/core-blocks/src/IconOrImage.tsx b/webapp/packages/core-blocks/src/IconOrImage.tsx index 73e8924828..b780eebdde 100644 --- a/webapp/packages/core-blocks/src/IconOrImage.tsx +++ b/webapp/packages/core-blocks/src/IconOrImage.tsx @@ -8,7 +8,7 @@ import { useMemo } from 'react'; -import { Icon } from './Icons'; +import { Icon } from './Icon'; import { StaticImage } from './StaticImage'; export interface IconOrImageProps { diff --git a/webapp/packages/core-blocks/src/Icons/index.ts b/webapp/packages/core-blocks/src/Icons/index.ts deleted file mode 100644 index e263cc0e6d..0000000000 --- a/webapp/packages/core-blocks/src/Icons/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Icon'; diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx index 085873b581..679aa343c4 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx @@ -15,7 +15,7 @@ import styled, { css, use } from 'reshadow'; import { composes, useStyles } from '@cloudbeaver/core-theming'; import { ShadowInput } from '../FormControls/ShadowInput'; -import { Icon } from '../Icons'; +import { Icon } from '../Icon'; import type { IProperty } from './IProperty'; import { PropertyValueSelector } from './PropertyValueSelector'; diff --git a/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx b/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx index 7199c5b859..5241ad9a15 100644 --- a/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx +++ b/webapp/packages/core-blocks/src/Slide/SlideOverlay.tsx @@ -10,7 +10,7 @@ import styled, { css } from 'reshadow'; import { composes, useStyles } from '@cloudbeaver/core-theming'; -import { Icon } from '../Icons'; +import { Icon } from '../Icon'; interface Props { className?: string; diff --git a/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx b/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx index 096ef92f19..da6fa24167 100644 --- a/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx +++ b/webapp/packages/core-blocks/src/Table/TableItemExpand.tsx @@ -12,7 +12,7 @@ import styled, { use } from 'reshadow'; import { useStyles } from '@cloudbeaver/core-theming'; -import { Icon } from '../Icons/Icon'; +import { Icon } from '../Icon'; import { TableContext } from './TableContext'; import { TableItemContext } from './TableItemContext'; diff --git a/webapp/packages/core-blocks/src/Tabs/Tab/Tab.tsx b/webapp/packages/core-blocks/src/Tabs/Tab/Tab.tsx index b7ffd6ce16..a33a2584be 100644 --- a/webapp/packages/core-blocks/src/Tabs/Tab/Tab.tsx +++ b/webapp/packages/core-blocks/src/Tabs/Tab/Tab.tsx @@ -14,7 +14,7 @@ import styled from 'reshadow'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; -import { Icon } from '../../Icons'; +import { Icon } from '../../Icon'; import { TabContext } from '../TabContext'; import type { TabProps } from './TabProps'; import { useTab } from './useTab'; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 2fbf33197e..6f223054ef 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -1,5 +1,5 @@ export * from './ComplexLoader'; -export * from './Icons'; +export * from './Icon'; export * from './ItemList/ItemList'; export * from './ItemList/ItemListSearch'; From 93572132dfa58efd4d4ba41586b82efd07f0cb23 Mon Sep 17 00:00:00 2001 From: Wroud Date: Sat, 19 Jun 2021 10:20:07 +0300 Subject: [PATCH 13/15] refactor: split Group component --- .../src/Containers/BASE_CONTAINERS_STYLES.ts | 12 +++++++++- .../core-blocks/src/Containers/Group.tsx | 24 +------------------ .../core-blocks/src/Containers/GroupClose.tsx | 20 ++++++++++++++++ webapp/packages/core-blocks/src/index.ts | 1 + 4 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 webapp/packages/core-blocks/src/Containers/GroupClose.tsx diff --git a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts index 98787268ca..523c998563 100644 --- a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts +++ b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts @@ -130,5 +130,15 @@ export const BASE_CONTAINERS_STYLES = composes( margin: 0; text-transform: uppercase; opacity: 0.9; - } + } + + GroupClose { + width: 18px; + height: 18px; + cursor: pointer; + display: flex; + position: absolute; + right: 24px; + margin-right: 0 !important; + } `); diff --git a/webapp/packages/core-blocks/src/Containers/Group.tsx b/webapp/packages/core-blocks/src/Containers/Group.tsx index dcbdf1967e..1d0b2084bf 100644 --- a/webapp/packages/core-blocks/src/Containers/Group.tsx +++ b/webapp/packages/core-blocks/src/Containers/Group.tsx @@ -7,9 +7,7 @@ */ import { forwardRef } from 'react'; -import styled, { css } from 'reshadow'; -import { Icon } from '../Icon'; import { filterContainerFakeProps } from './filterContainerFakeProps'; import type { IContainerProps } from './IContainerProps'; @@ -17,35 +15,15 @@ interface Props extends IContainerProps { form?: boolean; center?: boolean; box?: boolean; - onClose?: () => void; } -const style = css` - close { - width: 18px; - height: 18px; - cursor: pointer; - display: flex; - position: absolute; - right: 24px; - margin-right: 0 !important; - } -`; - export const Group = forwardRef>(function Group({ form, center, box, - children, - onClose, ...rest }, ref) { const divProps = filterContainerFakeProps(rest); - return styled(style)( -
- {onClose && } - {children} -
- ); + return
; }); diff --git a/webapp/packages/core-blocks/src/Containers/GroupClose.tsx b/webapp/packages/core-blocks/src/Containers/GroupClose.tsx new file mode 100644 index 0000000000..72398fd077 --- /dev/null +++ b/webapp/packages/core-blocks/src/Containers/GroupClose.tsx @@ -0,0 +1,20 @@ +/* + * 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 { Icon } from '../Icon'; + +interface IProps { + onClick?: () => void; +} + +export const GroupClose: React.FC> = function GroupClose({ + onClick, + ...rest +}) { + return ; +}; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 6f223054ef..dbc4da9f6c 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -99,6 +99,7 @@ export * from './FormControls/Filter'; export * from './Containers/BASE_CONTAINERS_STYLES'; export * from './Containers/Container'; export * from './Containers/Group'; +export * from './Containers/GroupClose'; export * from './Containers/GroupItem'; export * from './Containers/GroupTitle'; export * from './Containers/ColoredContainer'; From 7cfb7419fd5678472042d383e4b8a6230f830c06 Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 21 Jun 2021 14:04:27 +0300 Subject: [PATCH 14/15] refactor(core-app): CB-821 lazy tree update --- .../NodesManager/NavNodeManagerService.ts | 73 +------------------ .../shared/NodesManager/NavTreeResource.ts | 67 +++++++++++++++-- .../core-sdk/src/Resource/ResourceKeyList.ts | 9 +++ 3 files changed, 70 insertions(+), 79 deletions(-) diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index fded3c2dcb..e0a826b59a 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -6,8 +6,6 @@ * you may not use this file except in compliance with the License. */ -import { runInAction } from 'mobx'; - import { ConnectionAuthService, Connection, ConnectionInfoResource } from '@cloudbeaver/core-connections'; @@ -17,9 +15,7 @@ import { IExecutor, Executor, IExecutionContextProvider } from '@cloudbeaver/cor import { PermissionsService, EPermission, ServerService } from '@cloudbeaver/core-root'; -import { - resourceKeyList, ResourceKey, ResourceKeyUtils -} from '@cloudbeaver/core-sdk'; +import { resourceKeyList } from '@cloudbeaver/core-sdk'; import { NavigationService } from '@cloudbeaver/core-ui'; import { ENodeFeature } from './ENodeFeature'; @@ -116,11 +112,7 @@ export class NavNodeManagerService extends Bootstrap { .addHandler(this.navigateHandler.bind(this)); } - register(): void { - this.connectionInfo.onItemAdd.addHandler(this.connectionUpdateHandler.bind(this)); - this.connectionInfo.onItemDelete.addHandler(this.connectionRemoveHandler.bind(this)); - this.connectionInfo.onConnectionCreate.addHandler(this.connectionCreateHandler.bind(this)); - } + register(): void { } load(): void {} @@ -320,67 +312,6 @@ export class NavNodeManagerService extends Bootstrap { }; }; - private async connectionCreateHandler(connection: Connection) { - const enabled = await this.isNavTreeEnabled(); - if (!enabled) { - return; - } - - const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(connection.id); - this.navTree.markTreeOutdated(nodeId); - - const tree = await this.navTree.load(ROOT_NODE_PATH); - - if (!tree.includes(nodeId)) { - await this.navTree.refresh(ROOT_NODE_PATH); - } - } - - private async connectionUpdateHandler(key: ResourceKey) { - const enabled = await this.isNavTreeEnabled(); - if (!enabled) { - return; - } - - await this.navTree.load(ROOT_NODE_PATH); - - runInAction(() => { - ResourceKeyUtils.forEach(key, async key => { - const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); - - if (this.navTree.has(nodeId)) { - const connectionInfo = this.connectionInfo.get(key); - - if (!connectionInfo?.connected) { - this.removeTree(nodeId); - } else { - this.navTree.markTreeOutdated(nodeId); - } - } - - const nodeInfo = this.navNodeInfoResource.get(nodeId); - - if (nodeInfo) { - this.navTree.markOutdated(nodeInfo.parentId); - } - }); - }); - } - - private connectionRemoveHandler(key: ResourceKey) { - runInAction(() => { - ResourceKeyUtils.forEach(key, key => { - const navNodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); - - const node = this.getNode(navNodeId); - if (!node) { - return; - } - this.navTree.deleteInNode(node.parentId, [navNodeId]); - }); - }); - } - private async navigateHandler( data: INodeNavigationData, contexts: IExecutionContextProvider diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index a6ec51ffbd..7b89fbfdc6 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -6,8 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { action, computed, makeObservable } from 'mobx'; +import { action, computed, makeObservable, runInAction } from 'mobx'; +import { Connection, ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, IExecutor } from '@cloudbeaver/core-executor'; import { SessionDataResource } from '@cloudbeaver/core-root'; @@ -25,7 +26,8 @@ import { import { MetadataMap } from '@cloudbeaver/core-utils'; import { CoreSettingsService } from '../../CoreSettingsService'; -import { NavNodeInfoResource } from './NavNodeInfoResource'; +import { NavNodeInfoResource, ROOT_NODE_PATH } from './NavNodeInfoResource'; +import { NodeManagerUtils } from './NodeManagerUtils'; // TODO: so much dirty export interface NodePath { @@ -51,14 +53,16 @@ export class NavTreeResource extends CachedMapResource { private graphQLService: GraphQLService, private navNodeInfoResource: NavNodeInfoResource, private coreSettingsService: CoreSettingsService, - private sessionDataResource: SessionDataResource + private sessionDataResource: SessionDataResource, + private connectionInfo: ConnectionInfoResource ) { super(); - makeObservable(this, { + makeObservable(this, { childrenLimit: computed, setDetails: action, setNavObject: action, + connectionRemoveHandler: action.bound, }); this.metadata = new MetadataMap(() => ({ @@ -72,19 +76,22 @@ export class NavTreeResource extends CachedMapResource { this.onNodeRefresh = new Executor(null, (a, b) => a === b); this.onDataOutdated.addHandler(navNodeInfoResource.markOutdated.bind(navNodeInfoResource)); this.sessionDataResource.onDataUpdate.addPostHandler(() => this.markOutdated()); + this.connectionInfo.onItemAdd.addHandler(this.connectionUpdateHandler.bind(this)); + this.connectionInfo.onItemDelete.addHandler(this.connectionRemoveHandler); + this.connectionInfo.onConnectionCreate.addHandler(this.connectionCreateHandler.bind(this)); } async refreshTree(navNodeId: string): Promise { await this.graphQLService.sdk.navRefreshNode({ nodePath: navNodeId, }); - this.markTreeOutdated(navNodeId); + await this.markTreeOutdated(navNodeId); await this.refresh(navNodeId); await this.onNodeRefresh.execute(navNodeId); } - markTreeOutdated(navNodeId: ResourceKey): void { - this.markOutdated(resourceKeyList(this.getNestedChildren(navNodeId))); + async markTreeOutdated(navNodeId: ResourceKey): Promise { + await this.markOutdated(resourceKeyList(this.getNestedChildren(navNodeId))); } setDetails(keyObject: ResourceKey, state: boolean): void { @@ -179,7 +186,7 @@ export class NavTreeResource extends CachedMapResource { const allKeys = resourceKeyList(items); this.markUpdated(allKeys); this.onItemDelete.execute(allKeys); - this.navNodeInfoResource.delete(resourceKeyList(items.filter(navNodeId => navNodeId !== key))); + this.navNodeInfoResource.delete(ResourceKeyUtils.exclude(allKeys, key)); } protected async loader(key: ResourceKey): Promise> { @@ -216,6 +223,50 @@ export class NavTreeResource extends CachedMapResource { return nestedChildren; } + private async connectionUpdateHandler(key: ResourceKey) { + await this.markOutdated(ROOT_NODE_PATH); + + ResourceKeyUtils.forEach(key, async key => { + const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); + + if (this.has(nodeId)) { + const connectionInfo = this.connectionInfo.get(key); + + if (!connectionInfo?.connected) { + this.delete(nodeId); + } else { + this.markTreeOutdated(nodeId); + } + } + + const node = this.navNodeInfoResource.get(nodeId); + + if (node) { + this.markOutdated(node.parentId); + } + }); + } + + private connectionRemoveHandler(key: ResourceKey) { + runInAction(() => { + ResourceKeyUtils.forEach(key, key => { + const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(key); + + const node = this.navNodeInfoResource.get(nodeId); + + if (node) { + this.deleteInNode(node.parentId, [nodeId]); + } + }); + }); + } + + private async connectionCreateHandler(connection: Connection) { + const nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(connection.id); + await this.markOutdated(ROOT_NODE_PATH); + await this.markTreeOutdated(nodeId); + } + private setNavObject(data: NavNodeChildrenQuery | NavNodeChildrenQuery[]) { if (Array.isArray(data)) { for (const node of data) { diff --git a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts index c692f15f42..cd45d38608 100644 --- a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts +++ b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts @@ -46,6 +46,7 @@ export interface ResourceKeyUtils { every: (key: ResourceKey, predicate: (key: TKey, index: number) => boolean) => boolean; map: MapFnc; includes: (first: ResourceKey, second: ResourceKey) => boolean; + exclude: (first: ResourceKeyList, second: ResourceKey) => ResourceKey; } // eslint-disable-next-line @typescript-eslint/no-redeclare @@ -142,6 +143,14 @@ export const ResourceKeyUtils: ResourceKeyUtils = { return param === key; }, + + exclude(param: ResourceKeyList, key: ResourceKey): ResourceKey { + if (isResourceKeyList(key)) { + return resourceKeyList(param.list.filter(param => !key.list.includes(param)), param.mark); + } + + return resourceKeyList(param.list.filter(param => param !== key), param.mark); + }, }; export function isResourceKeyList(data: any): data is ResourceKeyList { From 34a56b18fdfe87e150184b447d5d3196f55cb392 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Mon, 21 Jun 2021 16:01:50 +0300 Subject: [PATCH 15/15] CB-1026 Root URI config fixed --- .../src/io/cloudbeaver/server/jetty/CBJettyServer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 410e0c584a..eaa1caa440 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 @@ -45,8 +45,9 @@ public class CBJettyServer { // Handler configuration ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS); servletContextHandler.setResourceBase(application.getContentRoot()); - servletContextHandler.setContextPath(application.getRootURI()); - servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), application.getRootURI()); + String rootURI = application.getRootURI(); + servletContextHandler.setContextPath(rootURI); + servletContextHandler.addServlet(new ServletHolder("static", new CBStaticServlet()), "/"); servletContextHandler.addServlet(new ServletHolder("images", new CBImageServlet()), application.getServicesURI() + "images/*"); servletContextHandler.addServlet(new ServletHolder("graphql", new GraphQLEndpoint()), application.getServicesURI() + "gql/*"); servletContextHandler.addEventListener(new CBServerContextListener());