From f162a98b7dfb23493d1eb38efd6ba809e33b4e86 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 22 Mar 2021 14:45:31 +0300 Subject: [PATCH 01/75] feat(plugin-aws-cloud-explorer): CB-830 select all select only visible nodes now --- .../core-utils/src/declensionOfNumber.ts | 25 +++++++++++++++++++ webapp/packages/core-utils/src/index.ts | 1 + 2 files changed, 26 insertions(+) create mode 100644 webapp/packages/core-utils/src/declensionOfNumber.ts diff --git a/webapp/packages/core-utils/src/declensionOfNumber.ts b/webapp/packages/core-utils/src/declensionOfNumber.ts new file mode 100644 index 0000000000..0ff755c7e3 --- /dev/null +++ b/webapp/packages/core-utils/src/declensionOfNumber.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +export function declensionOfNumber(value: number, words: string[]) { + value = Math.abs(value) % 100; + const num = value % 10; + if (value > 10 && value < 20) { + return words[2]; + } + + if (num > 1 && num < 5) { + return words[1]; + } + + if (num === 1) { + return words[0]; + } + + return words[2]; +} diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index 337dd0750e..a7307525ed 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -7,6 +7,7 @@ export * from './generateFileName'; export * from './md5'; export * from './TextTools'; export * from './copyToClipboard'; +export * from './declensionOfNumber'; export * from './Promises/CancellablePromise'; export * from './Promises/Deferred'; From 582686afd425d4319261ee9d18c9b67b6d7920aa Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Mon, 22 Mar 2021 16:17:38 +0300 Subject: [PATCH 02/75] CB-791 Auth tokens API fixes --- .../service/auth/WebUserAuthToken.java | 57 +++++++++++++++++++ .../cloudbeaver/service/auth/WebUserInfo.java | 8 ++- 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserAuthToken.java diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserAuthToken.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserAuthToken.java new file mode 100644 index 0000000000..fc6497ce4c --- /dev/null +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserAuthToken.java @@ -0,0 +1,57 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.cloudbeaver.service.auth; + +import io.cloudbeaver.model.session.WebAuthInfo; +import io.cloudbeaver.model.session.WebSession; +import io.cloudbeaver.model.user.WebUser; +import io.cloudbeaver.model.user.WebUserOriginInfo; + +import java.time.OffsetDateTime; + +/** + * WebUserInfo + */ +public class WebUserAuthToken { + + private final WebSession session; + private final WebUser user; + private final WebAuthInfo authInfo; + + public WebUserAuthToken(WebSession session, WebUser user, WebAuthInfo authInfo) { + this.session = session; + this.user = user; + this.authInfo = authInfo; + } + + public String getAuthProvider() { + return authInfo.getAuthProvider().getId(); + } + + public OffsetDateTime getLoginTime() { + return authInfo.getLoginTime(); + } + + public String getMessage() { + return authInfo.getMessage(); + } + + public WebUserOriginInfo getOrigin() { + return new WebUserOriginInfo(session, user, authInfo.getAuthProvider()); + } + +} diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java index 53aec5fc71..ce2972c7e0 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java @@ -16,12 +16,12 @@ */ package io.cloudbeaver.service.auth; -import io.cloudbeaver.model.session.WebAuthInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; import org.jkiss.dbeaver.Log; import java.util.List; +import java.util.stream.Collectors; /** * WebUserInfo @@ -46,8 +46,10 @@ public class WebUserInfo { return user.getDisplayName(); } - public List getAuthTokens() { - return session.getAllAuthInfo(); + public List getAuthTokens() { + return session.getAllAuthInfo().stream() + .map(ai -> new WebUserAuthToken(session, user, ai)) + .collect(Collectors.toList()); } } From 44c22e509ade86af1c99f4d14c3835e3ecb189d1 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Tue, 23 Mar 2021 13:15:32 +0300 Subject: [PATCH 03/75] CB-768 Save credentials locally fixed --- .../src/io/cloudbeaver/model/WebConnectionInfo.java | 5 +++++ .../service/core/impl/WebServiceCore.java | 13 ++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionInfo.java index fad24f06c3..9ffe17202d 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -297,6 +297,11 @@ public class WebConnectionInfo { this.savedNetworkCredentials = networkCredentials; } + public void clearSavedCredentials() { + this.savedAuthProperties = null; + this.savedNetworkCredentials = null; + } + @Property public Map getProviderProperties() { return dataSourceContainer.getConnectionConfiguration().getProviderProperties(); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index d9610d7cb4..e97e4da307 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -260,8 +260,6 @@ public class WebServiceCore implements DBWServiceCore { if (dataSourceContainer.isConnected()) { throw new DBWebException("Datasource '" + dataSourceContainer.getName() + "' is already connected"); } -// -// WebServiceUtils.initAuthProperties(dataSourceContainer, authProperties); boolean oldSavePassword = dataSourceContainer.isSavePassword(); try { @@ -270,7 +268,7 @@ public class WebServiceCore implements DBWServiceCore { throw new DBWebException("Error connecting to database", e); } finally { dataSourceContainer.setSavePassword(oldSavePassword); - connectionInfo.setSavedCredentials(null, null); + connectionInfo.clearSavedCredentials(); } // Mark all specified network configs as saved if (networkCredentials != null) { @@ -288,8 +286,13 @@ public class WebServiceCore implements DBWServiceCore { } if (saveCredentials != null && saveCredentials) { // Save all passed credentials in the datasource container + WebServiceUtils.saveAuthProperties( + dataSourceContainer, + dataSourceContainer.getConnectionConfiguration(), + authProperties, + true); + WebServiceUtils.saveCredentialsInDataSource(connectionInfo, dataSourceContainer, dataSourceContainer.getConnectionConfiguration()); - dataSourceContainer.setSavePassword(true); dataSourceContainer.persistConfiguration(); } @@ -519,7 +522,7 @@ public class WebServiceCore implements DBWServiceCore { webSession.removeConnection(connectionInfo); } else { // Just reset saved credentials - connectionInfo.setSavedCredentials(null, null); + connectionInfo.clearSavedCredentials(); } return connectionInfo; From 1e91885731f01a547817bef857366408e7989773 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Tue, 23 Mar 2021 23:21:28 +0300 Subject: [PATCH 04/75] CB-837 Server config API fixed --- .../io.cloudbeaver.service.admin/schema/service.admin.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 79107e1416..c3c02bbe76 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -57,7 +57,7 @@ input ServerConfigInput { publicCredentialsSaveEnabled: Boolean adminCredentialsSaveEnabled: Boolean - enabledAuthProviders: [ID!]! + enabledAuthProviders: [ID!] sessionExpireTime: Int } From 962bf08a7045f4b881830a6105ffc38b5e705a3f Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 24 Mar 2021 10:36:26 +0300 Subject: [PATCH 05/75] feat: CB-792 enable additional login --- webapp/.eslintrc.js | 2 +- .../AdministrationTools/AdministrationTools.tsx | 4 +++- .../core-authentication/src/AUTH_PROVIDER_LOCAL_ID.ts | 9 +++++++++ .../core-authentication/src/UserInfoResource.ts | 4 ---- .../packages/core-authentication/src/UsersResource.ts | 10 +++++----- webapp/packages/core-authentication/src/index.ts | 1 + webapp/packages/core-blocks/src/Loader/Loader.tsx | 3 ++- .../src/Administration/ConnectionsResource.ts | 3 ++- .../Users/UserForm/UserFormBaseBootstrap.ts | 3 ++- .../Users/UsersAdministrationController.ts | 4 ++-- .../src/Dialog/AuthDialogController.ts | 1 - 11 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 webapp/packages/core-authentication/src/AUTH_PROVIDER_LOCAL_ID.ts diff --git a/webapp/.eslintrc.js b/webapp/.eslintrc.js index 10d3d312f1..0e4c63b6be 100644 --- a/webapp/.eslintrc.js +++ b/webapp/.eslintrc.js @@ -85,7 +85,7 @@ module.exports = { ignoreTrailingComments: true, ignoreStrings: true, ignoreTemplateLiterals: true, - ignorePattern: "^export\\s(const\\s\\w+:?.+=.+function\\s|function\\s)\\w+\\s*\\(\\s*\\{$" + ignorePattern: "^export\\s(const\\s\\w+:?.+=.+function\\s|function\s)\\w+\\s*\\(.*\\{$" } ], 'import-helpers/order-imports': [ diff --git a/webapp/packages/core-administration/src/Administration/AdministrationTools/AdministrationTools.tsx b/webapp/packages/core-administration/src/Administration/AdministrationTools/AdministrationTools.tsx index e4fd13342b..095aa7b751 100644 --- a/webapp/packages/core-administration/src/Administration/AdministrationTools/AdministrationTools.tsx +++ b/webapp/packages/core-administration/src/Administration/AdministrationTools/AdministrationTools.tsx @@ -6,12 +6,14 @@ * you may not use this file except in compliance with the License. */ +import styled from 'reshadow'; + interface Props { className?: string; } export const AdministrationTools: React.FC = function AdministrationTools({ children, className }) { - return ( + return styled()( {children} diff --git a/webapp/packages/core-authentication/src/AUTH_PROVIDER_LOCAL_ID.ts b/webapp/packages/core-authentication/src/AUTH_PROVIDER_LOCAL_ID.ts new file mode 100644 index 0000000000..d514ef403c --- /dev/null +++ b/webapp/packages/core-authentication/src/AUTH_PROVIDER_LOCAL_ID.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export const AUTH_PROVIDER_LOCAL_ID = 'local'; diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index a2021d013d..599440439c 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -30,10 +30,6 @@ export class UserInfoResource extends CachedDataResource): Promise { - if (this.data) { - throw new Error('User already logged in'); - } - const processedCredentials = await this.authProviderService.processCredentials(provider, credentials); const { user } = await this.graphQLService.sdk.authLogin({ diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index be9a35c99e..30f1dbaa19 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -19,6 +19,7 @@ import { } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; +import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID'; import { AuthInfoService } from './AuthInfoService'; import { AuthProviderService } from './AuthProviderService'; @@ -72,7 +73,7 @@ export class UsersResource extends CachedMapResource): Promise { - const provider = 'local'; - const processedCredentials = await this.authProviderService.processCredentials(provider, credentials); + const processedCredentials = await this.authProviderService.processCredentials(AUTH_PROVIDER_LOCAL_ID, credentials); await this.graphQLService.sdk.setUserCredentials({ - providerId: provider, + providerId: AUTH_PROVIDER_LOCAL_ID, userId, credentials: processedCredentials, }); @@ -209,5 +209,5 @@ export class UsersResource extends CachedMapResource = observer(function Loader({ children, onCancel, }) { - let loaded = loading; + let loaded = !loading; if (state) { state = Array.isArray(state) ? state : [state]; for (const element of state) { if ('loading' in element) { loading = element.loading; + loaded = !loading; } else if ('isLoading' in element) { loaded = element.isLoaded(); loading = element.isLoading() || !loaded; diff --git a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts index a8b88daf10..c53e222cfb 100644 --- a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts +++ b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts @@ -9,6 +9,7 @@ import { action, makeObservable } from 'mobx'; import { Observable, Subject } from 'rxjs'; +import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { GraphQLService, @@ -241,7 +242,7 @@ export function isLocalConnection(connection: DatabaseConnection): boolean { if (!connection.origin) { return true; } - return connection.origin.type === 'local'; + return connection.origin.type === AUTH_PROVIDER_LOCAL_ID; } export function isCloudConnection(connection: DatabaseConnection): boolean { diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts index 573ba90f63..8473951812 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormBaseBootstrap.ts @@ -6,6 +6,7 @@ * you may not use this file except in compliance with the License. */ +import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { ConnectionAccess } from './ConnectionAccess'; @@ -32,7 +33,7 @@ export class UserFormBaseBootstrap extends Bootstrap { this.userFormService.tabsContainer.add({ key: 'origin', order: 2, - isHidden: (tabId, props) => props?.user.origin.type === 'local', + isHidden: (tabId, props) => props?.user.origin.type === AUTH_PROVIDER_LOCAL_ID, panel: () => OriginInfo, tab: () => OriginInfoTab, }); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts index 1ec14cb0a8..44257bab9b 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts @@ -8,7 +8,7 @@ import { observable, computed, makeObservable } from 'mobx'; -import { AdminUser, AuthProvidersResource, UsersResource } from '@cloudbeaver/core-authentication'; +import { AdminUser, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UsersResource } from '@cloudbeaver/core-authentication'; import { injectable, IInitializableController } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -40,7 +40,7 @@ export class UsersAdministrationController implements IInitializableController { } get isLocalProviderAvailable(): boolean { - return this.authProvidersResource.data.some(({ id }) => id === 'local'); + return this.authProvidersResource.data.some(({ id }) => id === AUTH_PROVIDER_LOCAL_ID); } get isLoading(): boolean { diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts index e138796914..ac118138e0 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts @@ -66,7 +66,6 @@ export class AuthDialogController implements IInitializableController, IDestruct this.isAuthenticating = true; try { - await this.authInfoService.logout(); await this.authInfoService.login(this.provider.id, this.credentials); this.close(); } catch (exception) { From b51d061d32bbff41cfc5e0cab24dd78acaf1c7c8 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 24 Mar 2021 10:55:20 +0300 Subject: [PATCH 06/75] refactor: CB-790 form's layout system --- .../src/Containers/BASE_CONTAINERS_STYLES.ts | 102 +++++------- .../core-blocks/src/Containers/Container.tsx | 4 +- .../core-blocks/src/Containers/Grid.tsx | 22 --- .../core-blocks/src/Containers/Group.tsx | 6 +- .../core-blocks/src/Containers/GroupItem.tsx | 4 +- .../core-blocks/src/Containers/GroupTitle.tsx | 4 +- .../src/Containers/IContainerProps.ts | 8 +- .../src/Containers/ILayoutSizeProps.ts | 16 ++ .../core-blocks/src/Containers/LayoutProps.ts | 33 ---- .../src/FormControls/Checkboxes/Checkbox.tsx | 99 ++++------- .../FormControls/Checkboxes/FieldCheckbox.tsx | 18 +- .../Checkboxes/FieldCheckboxNew.tsx | 17 +- .../FormControls/{ => Checkboxes}/Switch.tsx | 72 +++----- .../{ => Checkboxes}/SwitchNew.tsx | 72 +++----- .../Checkboxes/useCheckboxState.ts | 85 ++++++++++ .../src/FormControls/ComboboxNew.tsx | 3 +- .../src/FormControls/InputFieldNew.tsx | 4 +- .../src/FormControls/TextareaNew.tsx | 2 +- .../ObjectPropertyInfoForm.tsx | 2 +- .../ObjectPropertyInfoFormNew.tsx | 17 +- webapp/packages/core-blocks/src/index.ts | 8 +- .../ConnectionsTable/ConnectionEdit.tsx | 2 +- .../CreateConnection/CreateConnection.tsx | 2 +- .../src/ConnectionForm/Options/Options.tsx | 52 +++--- .../Options/ParametersFormNew.tsx | 13 +- .../src/ConnectionForm/SSH/SSH.tsx | 156 +++++++++--------- .../DatabaseAuthDialog/DatabaseAuthDialog.tsx | 2 +- .../src/DatabaseAuthDialog/SSHAuthForm.tsx | 2 +- .../src/styles/main/normalize.css | 2 +- .../Welcome/WelcomePage.tsx | 24 +-- .../Users/UserForm/UserInfo.tsx | 2 +- .../src/ConnectionDialog/ConnectionDialog.tsx | 12 +- 32 files changed, 398 insertions(+), 469 deletions(-) delete mode 100644 webapp/packages/core-blocks/src/Containers/Grid.tsx create mode 100644 webapp/packages/core-blocks/src/Containers/ILayoutSizeProps.ts delete mode 100644 webapp/packages/core-blocks/src/Containers/LayoutProps.ts rename webapp/packages/core-blocks/src/FormControls/{ => Checkboxes}/Switch.tsx (59%) rename webapp/packages/core-blocks/src/FormControls/{ => Checkboxes}/SwitchNew.tsx (62%) create mode 100644 webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts 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 932fc8e0a9..4cae52ae32 100644 --- a/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts +++ b/webapp/packages/core-blocks/src/Containers/BASE_CONTAINERS_STYLES.ts @@ -20,14 +20,22 @@ export const BASE_CONTAINERS_STYLES = composes( } `, css` - Container, ColoredContainer { + Container, ColoredContainer, Group { display: flex; - flex-direction: column; - flex-basis: 0; + flex-direction: row; align-content: baseline; - &[horizontal] { - flex-direction: row; + &[vertical] { + flex-direction: column; + align-content: stretch; + + & > :global(*) { + flex-basis: 0 !important; + } + } + + &[baseline] { + align-items: baseline; } &[wrap] { @@ -39,94 +47,64 @@ export const BASE_CONTAINERS_STYLES = composes( } &[parent] { - padding: 10px; + padding: 24px; } &[gap] { gap: 24px; } - - & > * { - flex-grow: 1; - } - - & > [flexItemKeepSize] { - flex-grow: 0; - } - - & > [flexItemTiny] { - flex-basis: 100px; - } - - & > [flexItemSmall] { - flex-basis: 240px; - } - - & > [flexItemMedium] { - flex-basis: 340px; - } - - & > [flexItemLarge] { - flex-basis: 540px; - } } - Grid, Group { - display: grid; - grid-gap: 24px; + Group { align-content: baseline; - grid-template-columns: minmax(min-content, 1fr); - grid-auto-rows: max-content; + box-sizing: border-box; + padding: 24px; + border-radius: 4px; - &[noGap] { - grid-gap: 0; - } - - &[horizontal] { - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + &[form] > :global(*) { + margin-right: 25%; } &[center] { margin: 0 auto; } - - & > [gridItemMax] { - grid-column: 1/-1; - } } - Group { - box-sizing: border-box; - margin: 10px; - padding: 24px; - border-radius: 4px; + Container, ColoredContainer, Group { + flex-wrap: wrap; + flex: 1 1 100%; - &[form] { - padding-right: 30%; + & > :global(*) { + flex: 1 1 100%; } - } - Container, ColoredContainer, Group, Grid { - flex-grow: 1; - - &[keepSize] { + &[keepSize], & > [keepSize] { flex-grow: 0; + flex-basis: 0; } - &[limitWidth] { - max-width: 800px; + &[tiny], & > [tiny] { + flex-basis: 140px; + max-width: 210px; } &[small], & > [small] { - max-width: 250px; + flex-basis: 260px; + max-width: 390px; } &[medium], & > [medium] { - max-width: 450px; + flex-basis: 460px; + max-width: 640px; } &[large], & > [large] { - max-width: 650px; + flex-basis: 800px; + max-width: 800px; + } + + &[fill], & > [fill] { + max-width: none; } } diff --git a/webapp/packages/core-blocks/src/Containers/Container.tsx b/webapp/packages/core-blocks/src/Containers/Container.tsx index bc8c18a183..59439b4907 100644 --- a/webapp/packages/core-blocks/src/Containers/Container.tsx +++ b/webapp/packages/core-blocks/src/Containers/Container.tsx @@ -8,9 +8,9 @@ import type { IContainerProps } from './IContainerProps'; -export const Container: React.FC = function Container({ children, className }) { +export const Container: React.FC = function Container({ children, ...rest }) { return ( -
+
{children}
); diff --git a/webapp/packages/core-blocks/src/Containers/Grid.tsx b/webapp/packages/core-blocks/src/Containers/Grid.tsx deleted file mode 100644 index a56392bac2..0000000000 --- a/webapp/packages/core-blocks/src/Containers/Grid.tsx +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 type { IGridOptions, ILayoutSizeProps } from './LayoutProps'; - -interface Props extends ILayoutSizeProps, IGridOptions { - className?: string; - limitWidth?: boolean; -} - -export const Grid: React.FC = function Grid({ children, className }) { - return ( -
- {children} -
- ); -}; diff --git a/webapp/packages/core-blocks/src/Containers/Group.tsx b/webapp/packages/core-blocks/src/Containers/Group.tsx index 66e8dc2db7..9f56c2222c 100644 --- a/webapp/packages/core-blocks/src/Containers/Group.tsx +++ b/webapp/packages/core-blocks/src/Containers/Group.tsx @@ -6,12 +6,12 @@ * you may not use this file except in compliance with the License. */ -import type { IGridOptions, ILayoutSizeProps } from './LayoutProps'; +import type { IContainerProps } from './IContainerProps'; -interface Props extends ILayoutSizeProps, IGridOptions { +interface Props extends IContainerProps { className?: string; form?: boolean; - keepSize?: boolean; + center?: boolean; } export const Group: React.FC = function Group({ children, className }) { diff --git a/webapp/packages/core-blocks/src/Containers/GroupItem.tsx b/webapp/packages/core-blocks/src/Containers/GroupItem.tsx index 1bb7664614..7fda0f808b 100644 --- a/webapp/packages/core-blocks/src/Containers/GroupItem.tsx +++ b/webapp/packages/core-blocks/src/Containers/GroupItem.tsx @@ -6,7 +6,9 @@ * you may not use this file except in compliance with the License. */ -interface Props { +import type { ILayoutSizeProps } from './ILayoutSizeProps'; + +interface Props extends ILayoutSizeProps { className?: string; } diff --git a/webapp/packages/core-blocks/src/Containers/GroupTitle.tsx b/webapp/packages/core-blocks/src/Containers/GroupTitle.tsx index b5cc1cd3dc..15d5df45f4 100644 --- a/webapp/packages/core-blocks/src/Containers/GroupTitle.tsx +++ b/webapp/packages/core-blocks/src/Containers/GroupTitle.tsx @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ -import type { IGridItemsLayoutProps } from './LayoutProps'; +import type { ILayoutSizeProps } from './ILayoutSizeProps'; -interface Props extends IGridItemsLayoutProps { +interface Props extends ILayoutSizeProps { className?: string; } diff --git a/webapp/packages/core-blocks/src/Containers/IContainerProps.ts b/webapp/packages/core-blocks/src/Containers/IContainerProps.ts index d6f35a50a9..7b3aaf17aa 100644 --- a/webapp/packages/core-blocks/src/Containers/IContainerProps.ts +++ b/webapp/packages/core-blocks/src/Containers/IContainerProps.ts @@ -6,11 +6,11 @@ * you may not use this file except in compliance with the License. */ -import type { IGridItemsLayoutProps, ILayoutSizeProps } from './LayoutProps'; +import type { ILayoutSizeProps } from './ILayoutSizeProps'; -export interface IContainerProps extends ILayoutSizeProps, IGridItemsLayoutProps { - className?: string; - horizontal?: boolean; +export interface IContainerProps extends ILayoutSizeProps, React.HTMLAttributes { + baseline?: boolean; + vertical?: boolean; wrap?: boolean; overflow?: boolean; parent?: boolean; diff --git a/webapp/packages/core-blocks/src/Containers/ILayoutSizeProps.ts b/webapp/packages/core-blocks/src/Containers/ILayoutSizeProps.ts new file mode 100644 index 0000000000..f81363ab72 --- /dev/null +++ b/webapp/packages/core-blocks/src/Containers/ILayoutSizeProps.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export interface ILayoutSizeProps { + keepSize?: boolean; + tiny?: boolean; + small?: boolean; + medium?: boolean; + large?: boolean; + fill?: boolean; +} diff --git a/webapp/packages/core-blocks/src/Containers/LayoutProps.ts b/webapp/packages/core-blocks/src/Containers/LayoutProps.ts deleted file mode 100644 index 54707182ef..0000000000 --- a/webapp/packages/core-blocks/src/Containers/LayoutProps.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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. - */ - -export interface ILayoutSizeProps { - keepSize?: boolean; - limitWidth?: boolean; - small?: boolean; - medium?: boolean; - large?: boolean; - grow?: boolean; -} - -export interface IGridItemsLayoutProps { - gridItemMax?: boolean; -} - -export interface IFlexItemsLayoutProps { - flexItemKeepSize?: boolean; - flexItemTiny?: boolean; - flexItemSmall?: boolean; - flexItemMedium?: boolean; - flexItemLarge?: boolean; -} -export interface IGridOptions { - noGap?: boolean; - horizontal?: boolean; - center?: boolean; -} diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx index 9b32abc2f9..02dded83d6 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx @@ -7,119 +7,76 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback, useContext, useState } from 'react'; -import type { IGridItemsLayoutProps } from '../../Containers/LayoutProps'; -import { FormContext } from '../FormContext'; +import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps'; import { isControlPresented } from '../isControlPresented'; import { CheckboxMarkup } from './CheckboxMarkup'; +import { CheckboxOnChangeEvent, useCheckboxState } from './useCheckboxState'; export type CheckboxMod = 'primary' | 'small'; -export type CheckboxBaseProps = Omit, 'onChange' | 'type' | 'value' | 'checked'> & IGridItemsLayoutProps & { - value?: string; - checkboxLabel?: string; - long?: boolean; +export interface CheckboxBaseProps { + label?: string; mod?: CheckboxMod[]; ripple?: boolean; -}; - -type CheckboxOnChangeEvent = - ((value: boolean, name: T) => void) | - ((value: boolean, name: T) => boolean); - -export type CheckboxControlledProps = CheckboxBaseProps & { - checked?: boolean; indeterminate?: boolean; - onChange?: CheckboxOnChangeEvent; +} + +export type CheckboxInputProps = Omit, 'onChange' | 'type' | 'value' | 'checked' | 'id'> & ILayoutSizeProps; + +export interface ICheckboxControlledProps extends CheckboxInputProps { + value?: string; state?: never; + checked?: boolean; + onChange?: CheckboxOnChangeEvent; autoHide?: never; -}; +} -export type CheckboxObjectProps = CheckboxBaseProps & { - name: TKey; - state: TState; - onChange?: CheckboxOnChangeEvent; +export interface ICheckboxObjectProps extends CheckboxInputProps { + value?: string; + state: Partial>; checked?: never; - indeterminate?: boolean; + onChange?: CheckboxOnChangeEvent; autoHide?: boolean; -}; + name: TKey; +} export interface CheckboxType { - (props: CheckboxControlledProps): React.ReactElement | null; - (props: CheckboxObjectProps): React.ReactElement | null; + (props: CheckboxBaseProps & ICheckboxControlledProps): React.ReactElement | null; + (props: CheckboxBaseProps & ICheckboxObjectProps): React.ReactElement | null; } export const Checkbox: CheckboxType = observer(function Checkbox({ name, value, state, - checkboxLabel, - checked: checkedControlled, + label, + checked, children, mod, ripple, className, - long, autoHide, onChange, ...rest -}: CheckboxControlledProps | CheckboxObjectProps) { - const [count, refresh] = useState(0); - const context = useContext(FormContext); - const handleChange = useCallback((event: React.ChangeEvent) => { - if (state) { - if (Array.isArray(state[name])) { - state[name] = (state[name] as string[]).filter(item => item !== value); - if (event.target.checked) { - state[name].push(value); - } - } else { - state[name] = event.target.checked; - } - } - if (onChange) { - const checked = onChange(event.target.checked, name); - - if (typeof checked === 'boolean') { - event.target.checked = checked; - } - } - if (context) { - context.onChange(event.target.checked, name); - } - refresh(count + 1); - }, [state, name, value, onChange, context, count]); +}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { + const checkboxState = useCheckboxState({ value, checked, state, name, onChange }); if (autoHide && !isControlPresented(name, state)) { return null; } - let checked = checkedControlled; - - if (state) { - if (typeof state[name] === 'string') { - checked = state[name] === 'true'; - } else { - checked = state[name]; - } - } - - if (Array.isArray(checked)) { - checked = checked.includes(value); - } - return ( ); }); diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx index 7d99afd0f4..9e2cf2b8bf 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckbox.tsx @@ -6,13 +6,13 @@ * you may not use this file except in compliance with the License. */ -import styled, { use, css } from 'reshadow'; +import styled, { css } from 'reshadow'; import { useStyles } from '@cloudbeaver/core-theming'; import { baseFormControlStyles } from '../baseFormControlStyles'; import { isControlPresented } from '../isControlPresented'; -import { Checkbox, CheckboxType, CheckboxControlledProps, CheckboxObjectProps } from './Checkbox'; +import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'; const fieldCheckboxStyles = css` Checkbox { @@ -21,26 +21,20 @@ const fieldCheckboxStyles = css` `; export const FieldCheckbox: CheckboxType = function FieldCheckbox({ - checked: checkedControlled, children, className, - long, - autoHide, ...rest -}: CheckboxControlledProps | CheckboxObjectProps) { +}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { const styles = useStyles(baseFormControlStyles, fieldCheckboxStyles); - if (autoHide && !isControlPresented(rest.name, rest.state)) { + if (rest.autoHide && !isControlPresented(rest.name, rest.state)) { return null; } return styled(styles)( - + {children} - + ); }; diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx index a48f10bf75..82c135094e 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx @@ -12,7 +12,7 @@ import { useStyles } from '@cloudbeaver/core-theming'; import { baseFormControlStylesNew } from '../baseFormControlStylesNew'; import { isControlPresented } from '../isControlPresented'; -import { Checkbox, CheckboxType, CheckboxControlledProps, CheckboxObjectProps } from './Checkbox'; +import { Checkbox, CheckboxBaseProps, CheckboxType, ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'; const fieldCheckboxStyles = css` Checkbox { @@ -24,31 +24,28 @@ const fieldCheckboxStyles = css` white-space: pre-wrap; } field-label { - padding-left: 10px; composes: theme-typography--body2 from global; + cursor: pointer; + user-select: none; + padding-left: 10px; line-height: 16px; } `; export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({ - checked: checkedControlled, children, className, - autoHide, ...rest -}: CheckboxControlledProps | CheckboxObjectProps) { +}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { const styles = useStyles(baseFormControlStylesNew, fieldCheckboxStyles); - if (autoHide && !isControlPresented(rest.name, rest.state)) { + if (rest.autoHide && !isControlPresented(rest.name, rest.state)) { return null; } return styled(styles)( - + {children} ); diff --git a/webapp/packages/core-blocks/src/FormControls/Switch.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Switch.tsx similarity index 59% rename from webapp/packages/core-blocks/src/FormControls/Switch.tsx rename to webapp/packages/core-blocks/src/FormControls/Checkboxes/Switch.tsx index 2e9de5093b..75e8698515 100644 --- a/webapp/packages/core-blocks/src/FormControls/Switch.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Switch.tsx @@ -7,14 +7,14 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback, useContext } from 'react'; -import styled, { css, use } from 'reshadow'; +import styled, { css } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; -import { baseFormControlStyles } from './baseFormControlStyles'; -import { FormContext } from './FormContext'; -import { isControlPresented } from './isControlPresented'; +import { baseFormControlStyles } from '../baseFormControlStyles'; +import { isControlPresented } from '../isControlPresented'; +import type { ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'; +import { useCheckboxState } from './useCheckboxState'; const switchStyles = composes( css` @@ -77,79 +77,47 @@ const switchState = { ), }; -type BaseProps = Omit, 'onChange' | 'type' | 'value' | 'checked'> & { +interface IBaseProps { mod?: Array; label?: string; description?: string; - long?: boolean; -}; - -type ControlledProps = BaseProps & { - checked?: boolean; - onChange?: (value: boolean, name?: string) => any; - - state?: never; - autoHide?: never; -}; - -type ObjectProps = BaseProps & { - name: TKey; - state: TState; - onChange?: (value: boolean, name: TKey) => any; - autoHide?: boolean; - - checked?: never; -}; +} interface SwitchType { - (props: ControlledProps): React.ReactElement | null; - (props: ObjectProps): React.ReactElement | null; + (props: IBaseProps & ICheckboxControlledProps): React.ReactElement | null; + (props: IBaseProps & ICheckboxObjectProps): React.ReactElement | null; } export const Switch: SwitchType = observer(function Switch({ name, - id, + value, label, description, state, - checked: checkedControlled, + checked, className, children, onChange, mod = [], - long, autoHide, disabled, ...rest -}: ControlledProps | ObjectProps) { - const context = useContext(FormContext); - const checked = state ? state[name] : checkedControlled; +}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { + const checkboxState = useCheckboxState({ value, checked, state, name, onChange }); const styles = useStyles( baseFormControlStyles, switchStyles, ...mod.map(mod => switchMod[mod]), disabled && switchState.disabled, - checked && switchState.checked + checkboxState.checked && switchState.checked ); - const handleChange = useCallback((event: React.ChangeEvent) => { - if (state) { - state[name] = event.target.checked; - } - if (onChange) { - onChange(event.target.checked, name); - } - if (context) { - context.onChange(event.target.checked, name); - } - }, [state, name, context, onChange]); - if (autoHide && !isControlPresented(name, state)) { return null; } return styled(styles)( - + {children} @@ -159,16 +127,16 @@ export const Switch: SwitchType = observer(function Switch({ as='input' {...rest} type="checkbox" - id={id} + id={value || name} role="switch" - aria-checked={checked} - checked={checked} + aria-checked={checkboxState.checked} + checked={checkboxState.checked} disabled={disabled} - onChange={handleChange} + onChange={checkboxState.change} /> - + {description} ); diff --git a/webapp/packages/core-blocks/src/FormControls/SwitchNew.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/SwitchNew.tsx similarity index 62% rename from webapp/packages/core-blocks/src/FormControls/SwitchNew.tsx rename to webapp/packages/core-blocks/src/FormControls/Checkboxes/SwitchNew.tsx index 84bfb7e342..02a797cb9e 100644 --- a/webapp/packages/core-blocks/src/FormControls/SwitchNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/SwitchNew.tsx @@ -7,14 +7,14 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback, useContext } from 'react'; import styled, { css } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; -import { baseFormControlStylesNew } from './baseFormControlStylesNew'; -import { FormContext } from './FormContext'; -import { isControlPresented } from './isControlPresented'; +import { baseFormControlStylesNew } from '../baseFormControlStylesNew'; +import { isControlPresented } from '../isControlPresented'; +import type { ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'; +import { useCheckboxState } from './useCheckboxState'; const switchStyles = composes( css` @@ -43,11 +43,13 @@ const switchStyles = composes( align-items: center; } field-label { + composes: theme-typography--body1 from global; + cursor: pointer; + user-select: none; display: block; - padding-right: 17px; + padding-left: 18px; min-width: 50px; white-space: pre-wrap; - composes: theme-typography--body1 from global; font-weight: 500; } ` @@ -80,39 +82,22 @@ const switchState = { ), }; -type BaseProps = Omit, 'onChange' | 'type' | 'value' | 'checked'> & { +interface IBaseProps { mod?: Array; description?: string; -}; - -type ControlledProps = BaseProps & { - checked?: boolean; - onChange?: (value: boolean, name?: string) => any; - - state?: never; - autoHide?: never; -}; - -type ObjectProps = BaseProps & { - name: TKey; - state: TState; - onChange?: (value: boolean, name: TKey) => any; - autoHide?: boolean; - - checked?: never; -}; +} interface SwitchType { - (props: ControlledProps): React.ReactElement | null; - (props: ObjectProps): React.ReactElement | null; + (props: IBaseProps & ICheckboxControlledProps): React.ReactElement | null; + (props: IBaseProps & ICheckboxObjectProps): React.ReactElement | null; } export const SwitchNew: SwitchType = observer(function SwitchNew({ name, - id, + value, description, state, - checked: checkedControlled, + checked, className, children, onChange, @@ -120,29 +105,16 @@ export const SwitchNew: SwitchType = observer(function SwitchNew({ autoHide, disabled, ...rest -}: ControlledProps | ObjectProps) { - const context = useContext(FormContext); - const checked = state ? state[name] : checkedControlled; +}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps)) { + const checkboxState = useCheckboxState({ value, checked, state, name, onChange }); const styles = useStyles( baseFormControlStylesNew, switchStyles, ...mod.map(mod => switchMod[mod]), disabled && switchState.disabled, - checked && switchState.checked + checkboxState.checked && switchState.checked ); - const handleChange = useCallback((event: React.ChangeEvent) => { - if (state) { - state[name] = event.target.checked; - } - if (onChange) { - onChange(event.target.checked, name); - } - if (context) { - context.onChange(event.target.checked, name); - } - }, [state, name, context, onChange]); - if (autoHide && !isControlPresented(name, state)) { return null; } @@ -150,7 +122,6 @@ export const SwitchNew: SwitchType = observer(function SwitchNew({ return styled(styles)( - {children} @@ -159,15 +130,16 @@ export const SwitchNew: SwitchType = observer(function SwitchNew({ as='input' {...rest} type="checkbox" - id={id} + id={value || name} role="switch" - aria-checked={checked} - checked={checked} + aria-checked={checkboxState.checked} + checked={checkboxState.checked} disabled={disabled} - onChange={handleChange} + onChange={checkboxState.change} /> + {children} {description} diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts b/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts new file mode 100644 index 0000000000..da13b42b61 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts @@ -0,0 +1,85 @@ +/* + * 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 { useContext, useState } from 'react'; + +import { useObjectRef } from '../../useObjectRef'; +import { FormContext } from '../FormContext'; + +export type CheckboxOnChangeEvent =(value: boolean, name: T) => void; + +export type CheckboxStateOptions = { + value: string | undefined; + checked: boolean | undefined; +} & ( + { + state: undefined; + name: string | undefined; + onChange: CheckboxOnChangeEvent | undefined; + } + | { + state: Record | undefined; + name: TKey; + onChange: CheckboxOnChangeEvent | undefined; + } +); + +interface ICheckboxState { + checked: boolean; + change: (event: React.ChangeEvent) => void; +} + +export function useCheckboxState(options: CheckboxStateOptions): ICheckboxState { + const [count, refresh] = useState(0); + const context = useContext(FormContext); + const optionsRef = useObjectRef({ ...options, context, count }); + const { state, name, value } = optionsRef; + + let checked = optionsRef.checked ?? false; + + if (state !== undefined && name !== undefined) { + const currentState = state[name as TKey]; + + if (typeof value === 'string' && Array.isArray(currentState)) { + checked = currentState.includes(value); + } else if (typeof currentState === 'string') { + checked = currentState.toLowerCase() === 'true'; + } else { + checked = !!currentState; + } + } + + return useObjectRef({ + checked, + change(event: React.ChangeEvent) { + const { state, name, value, onChange, count, context } = optionsRef; + const checked = event.target.checked; + + if (state !== undefined && name !== undefined) { + const currentState = state[name as TKey]; + + if (typeof value === 'string' && Array.isArray(currentState)) { + const elementIndex = currentState.indexOf(value); + + if (checked && elementIndex === -1) { + currentState.push(value); + } else if (elementIndex !== -1) { + currentState.splice(elementIndex, 1); + } + } else { + state[name as TKey] = checked; + } + } + + onChange?.(checked, name as TKey); + context?.onChange(checked, name); + + refresh(count + 1); + }, + }, { checked }); +} diff --git a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx index b6a27894f1..5c5c9056cc 100644 --- a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx @@ -20,6 +20,7 @@ import styled, { css, use } from 'reshadow'; import { useStyles, composes } from '@cloudbeaver/core-theming'; +import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import { IconButton } from '../IconButton'; import { Icon } from '../Icons/Icon'; import { baseFormControlStylesNew } from './baseFormControlStylesNew'; @@ -100,7 +101,7 @@ const styles = composes( ` ); -type BaseProps = Omit, 'onChange' | 'onSelect' | 'name' | 'value'> & { +type BaseProps = Omit, 'onChange' | 'onSelect' | 'name' | 'value'> & ILayoutSizeProps & { propertyName?: string; items: TValue[]; searchable?: boolean; diff --git a/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx b/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx index 46ad1781dc..ed43374ee5 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx @@ -12,7 +12,7 @@ import styled, { use, css } from 'reshadow'; import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; -import type { IFlexItemsLayoutProps, IGridItemsLayoutProps, ILayoutSizeProps } from '../Containers/LayoutProps'; +import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import { baseFormControlStylesNew } from './baseFormControlStylesNew'; import { FormContext } from './FormContext'; import { isControlPresented } from './isControlPresented'; @@ -28,7 +28,7 @@ const INPUT_FIELD_STYLES = css` } `; -type BaseProps = Omit, 'onChange' | 'name' | 'value'> & ILayoutSizeProps & IGridItemsLayoutProps & IFlexItemsLayoutProps & { +type BaseProps = Omit, 'onChange' | 'name' | 'value'> & ILayoutSizeProps & { description?: string; mod?: 'surface'; ref?: React.Ref; diff --git a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx index d56814fa3e..bc532f9446 100644 --- a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx @@ -12,7 +12,7 @@ import styled, { css, use } from 'reshadow'; import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; -import type { ILayoutSizeProps } from '../Containers/LayoutProps'; +import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import { baseFormControlStylesNew } from './baseFormControlStylesNew'; import { FormContext } from './FormContext'; diff --git a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx index 255a5eba99..5d465f85c0 100644 --- a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx +++ b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx @@ -82,7 +82,7 @@ const RenderField: React.FC = observer(function RenderField({ diff --git a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew.tsx b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew.tsx index 03a00bbb8a..d2bfa23e56 100644 --- a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew.tsx +++ b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew.tsx @@ -11,6 +11,7 @@ import { useCallback } from 'react'; import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; +import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; import { FieldCheckboxNew } from '../FormControls/Checkboxes/FieldCheckboxNew'; import { ComboboxNew } from '../FormControls/ComboboxNew'; import { FormFieldDescriptionNew } from '../FormControls/FormFieldDescriptionNew'; @@ -31,6 +32,7 @@ interface RenderFieldProps { autoHide?: boolean; showRememberTip?: boolean; onFocus?: (event: React.FocusEvent) => void; + className?: string; } const RenderField: React.FC = observer(function RenderField({ @@ -43,6 +45,7 @@ const RenderField: React.FC = observer(function RenderField({ autoHide, showRememberTip, onFocus, + className, }) { const href = property.features.includes('href'); const password = property.features.includes('password'); @@ -52,7 +55,7 @@ const RenderField: React.FC = observer(function RenderField({ if (href) { return ( - + {property.description} ); @@ -63,7 +66,7 @@ const RenderField: React.FC = observer(function RenderField({ return null; } return ( - + {state[property.id!]} ); @@ -80,7 +83,9 @@ const RenderField: React.FC = observer(function RenderField({ state={state} title={property.description} disabled={disabled || readOnly} - >{property.displayName ?? ''} + className={className} + > + {property.displayName ?? ''} ); } @@ -96,6 +101,7 @@ const RenderField: React.FC = observer(function RenderField({ defaultValue={property.defaultValue} title={property.description} disabled={disabled} + className={className} > {property.displayName ?? ''} @@ -113,6 +119,7 @@ const RenderField: React.FC = observer(function RenderField({ autoHide={autoHide} autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`} mod='surface' + className={className} onFocus={onFocus} > {property.displayName} @@ -120,7 +127,7 @@ const RenderField: React.FC = observer(function RenderField({ ); }); -interface ObjectPropertyFormProps { +interface ObjectPropertyFormProps extends ILayoutSizeProps { properties: ObjectPropertyInfo[] | undefined; state: Record; editable?: boolean; @@ -137,6 +144,7 @@ export const ObjectPropertyInfoFormNew: React.FC = obse properties, state, editable = true, + className, autofillToken = '', disabled, readOnly, @@ -159,6 +167,7 @@ export const ObjectPropertyInfoFormNew: React.FC = obse {properties.map(property => ( = obse return styled(useStyles(styles, BASE_CONTAINERS_STYLES))( - - - - + + + + = obse valueSelector={driver => driver?.name ?? ''} readOnly={form.form.readonly || edit || drivers.length < 2} disabled={form.form.disabled} + tiny > {translate('connections_connection_driver')} @@ -178,11 +178,12 @@ export const Options: TabContainerPanelComponent = obse disabled={form.form.disabled} readOnly={form.form.readonly} mod='surface' + tiny required > {translate('connections_connection_name')} - + {JDBC ? ( = obse disabled={edit || form.form.disabled} readOnly={form.form.readonly} // autoHide={} // maybe better to use autoHide - >{translate('connections_connection_template')} + > + {translate('connections_connection_template')} )} = obse - + {(authModel && !driver.data?.anonymousAccess) && ( - - {translate('connections_connection_edit_authentication')} - + + {translate('connections_connection_edit_authentication')} + + + {credentialsSavingEnabled && ( {translate('connections_connection_edit_save_credentials')} )} )} {driver.isLoaded() && driver.data?.providerProperties && driver.data.providerProperties.length > 0 && ( - + {translate('connections_connection_edit_settings')} {booleanProviderProperties && booleanProviderProperties.length > 0 && ( - + )} {providerPropertiesWithoutBoolean && ( - + - + )} )} diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/ParametersFormNew.tsx b/webapp/packages/core-connections/src/ConnectionForm/Options/ParametersFormNew.tsx index d053dfeea7..8e18a2d247 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/ParametersFormNew.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/ParametersFormNew.tsx @@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { BASE_CONTAINERS_STYLES, Container, Grid, InputFieldNew } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, Container, InputFieldNew } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { ConnectionConfig } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -32,16 +32,16 @@ export const ParametersFormNew = observer(function ParametersFormNew({ const translate = useTranslate(); return styled(useStyles(BASE_CONTAINERS_STYLES))( - + {!embedded && ( - + {translate('customConnection_custom_host')} @@ -52,7 +52,7 @@ export const ParametersFormNew = observer(function ParametersFormNew({ state={config} disabled={disabled} readOnly={readOnly || !originLocal} - flexItemTiny + tiny > {translate('customConnection_custom_port')} @@ -64,10 +64,9 @@ export const ParametersFormNew = observer(function ParametersFormNew({ state={config} disabled={disabled} readOnly={readOnly} - gridItemMax > {translate('customConnection_custom_database')} - + ); }); diff --git a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx index 782230a28e..fe704103ee 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx @@ -95,86 +95,84 @@ export const SSH: TabContainerPanelComponent = observer return styled(styles)( - - - + + {translate('connections_network_handler_ssh_tunnel_enable')} + + + - {translate('connections_network_handler_ssh_tunnel_enable')} - - - - {translate('connections_network_handler_ssh_tunnel_host')} - - - {translate('connections_network_handler_ssh_tunnel_port')} - - - - - {translate('connections_network_handler_ssh_tunnel_user')} - - - {translate('connections_network_handler_ssh_tunnel_password')} - - - {credentialsSavingEnabled && ( - {translate('connections_network_handler_ssh_tunnel_save_password')} - - )} - - - - - + {translate('connections_network_handler_ssh_tunnel_host')} + + + {translate('connections_network_handler_ssh_tunnel_port')} + + + + + {translate('connections_network_handler_ssh_tunnel_user')} + + + {translate('connections_network_handler_ssh_tunnel_password')} + + + {credentialsSavingEnabled && ( + {translate('connections_network_handler_ssh_tunnel_save_password')} + + )} + + + + ); diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx index 8ae53df07a..f4d851298a 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx @@ -124,7 +124,7 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({ diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx index 9adc3657ff..16e52cd3a6 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx @@ -79,7 +79,7 @@ export const SSHAuthForm: React.FC = observer(function SSHAuthForm({ name="savePassword" value={sshHandlerId + ' savePassword'} state={state} - checkboxLabel={translate('connections_network_handler_ssh_tunnel_save_password')} + label={translate('connections_network_handler_ssh_tunnel_save_password')} disabled={disabled} /> diff --git a/webapp/packages/core-theming/src/styles/main/normalize.css b/webapp/packages/core-theming/src/styles/main/normalize.css index 5e1a0484d4..8e4b4da433 100644 --- a/webapp/packages/core-theming/src/styles/main/normalize.css +++ b/webapp/packages/core-theming/src/styles/main/normalize.css @@ -379,7 +379,7 @@ template { */ [hidden] { - display: none; + display: none !important; } .icon { diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/Welcome/WelcomePage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/Welcome/WelcomePage.tsx index 1062d56acc..c56ada831c 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/Welcome/WelcomePage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/Welcome/WelcomePage.tsx @@ -8,7 +8,7 @@ import styled, { css } from 'reshadow'; -import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, GroupItem } from '@cloudbeaver/core-blocks'; import { Translate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -25,20 +25,14 @@ const styles = css` export const WelcomePage: React.FC = function WelcomePage() { return styled(useStyles(styles, BASE_CONTAINERS_STYLES))( - - - - - -

-
- -

- -
-
-
-
+ + +

+ +

+ +
+
); }; diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx index 61b575f16e..f6247dbba0 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx @@ -100,7 +100,7 @@ export const UserInfo: TabContainerPanelComponent = observer(fun handleRoleChange(role.roleId, checked)} diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx index b1161b584d..fe77281104 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx @@ -11,7 +11,15 @@ import styled, { css } from 'reshadow'; import { useAdministrationSettings } from '@cloudbeaver/core-administration'; import { - ErrorMessage, SubmittingForm, Loader, useFocus, ObjectPropertyInfoForm, FormBox, FormBoxElement, FormGroup, FieldCheckbox + ErrorMessage, + SubmittingForm, + Loader, + useFocus, + ObjectPropertyInfoForm, + FormBox, + FormBoxElement, + FormGroup, + FieldCheckbox } from '@cloudbeaver/core-blocks'; import { SSH_TUNNEL_ID, SSHAuthForm } from '@cloudbeaver/core-connections'; import { useController } from '@cloudbeaver/core-di'; @@ -112,7 +120,7 @@ export const ConnectionDialog = observer(function ConnectionDialog({ From 99a20c92c7e5480e5c5ee00156ba58abaf3e18be Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 24 Mar 2021 11:39:00 +0300 Subject: [PATCH 07/75] refactor: CB-794 placeholder context --- .../src/Placeholder/Placeholder.tsx | 15 +- .../src/Placeholder/PlaceholderContainer.ts | 10 +- .../ConnectionsAdministrationService.ts | 6 +- .../ConnectionsTable/Connection.tsx | 5 +- .../ConnectionDetailsInfo/Origin.tsx | 19 ++- .../ConnectionDetailsInfo/SSH.tsx | 12 +- .../ConnectionDetailsInfo/Template.tsx | 12 +- .../src/ConnectionForm/ConnectionForm.tsx | 2 +- .../ServerConfigurationPage.tsx | 150 ++++++++---------- .../ServerConfigurationPageController.ts | 7 +- .../Users/UsersAdministrationService.ts | 6 +- .../Administration/Users/UsersTable/User.tsx | 2 +- .../UsersTable/UserDetailsInfo/Origin.tsx | 16 +- .../TableViewer/TableHeader/TableHeader.tsx | 2 +- .../TableHeader/TableHeaderService.ts | 6 +- .../TableHeader/TableWhereFilter.tsx | 35 ++-- 16 files changed, 140 insertions(+), 165 deletions(-) diff --git a/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx b/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx index c7b4543653..c1db2d9cef 100644 --- a/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx +++ b/webapp/packages/core-blocks/src/Placeholder/Placeholder.tsx @@ -10,18 +10,17 @@ import { observer } from 'mobx-react-lite'; import type { PlaceholderContainer } from './PlaceholderContainer'; -type Props = { +type Props> = T & { container: PlaceholderContainer; -} & (T extends undefined ? { - context?: undefined; -} : { - context: T; -}); +}; -export const Placeholder = observer(function Placeholder({ container, context }: Props) { +export const Placeholder = observer(function Placeholder>({ + container, + ...rest +}: Props) { return ( <> - {container.get().map(({ id, component: Component }) => )} + {container.get().map(({ id, component: Component }) => )} ); }); diff --git a/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts b/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts index 599b07c965..60b29e1471 100644 --- a/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts +++ b/webapp/packages/core-blocks/src/Placeholder/PlaceholderContainer.ts @@ -10,19 +10,15 @@ import { observable } from 'mobx'; import { uuid } from '@cloudbeaver/core-utils'; -export interface PlaceholderProps { - context: T; -} +export type PlaceholderComponent = Record> = React.FunctionComponent; -export type PlaceholderComponent = React.FunctionComponent>; - -export interface PlaceholderElement { +export interface PlaceholderElement = Record> { id: string; component: PlaceholderComponent; order?: number; } -export class PlaceholderContainer { +export class PlaceholderContainer = Record> { private placeholders = observable>([], { deep: false }); get(): Array> { diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts index 4cd9e1f1f8..4389dc6c90 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts @@ -22,9 +22,13 @@ import { SSH } from './ConnectionsTable/ConnectionDetailsInfo/SSH'; import { Template } from './ConnectionsTable/ConnectionDetailsInfo/Template'; import { CreateConnectionService } from './CreateConnectionService'; +export interface IConnectionDetailsPlaceholderProps { + connection: DatabaseConnection; +} + @injectable() export class ConnectionsAdministrationService extends Bootstrap { - readonly connectionDetailsPlaceholder = new PlaceholderContainer(); + readonly connectionDetailsPlaceholder = new PlaceholderContainer(); constructor( private administrationItemService: AdministrationItemService, diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx index 54d6c4aaa5..089fb892c6 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/Connection.tsx @@ -61,7 +61,10 @@ export const Connection = observer(function Connection({ connection }: Props) { {connection.name} {connection.host}{connection.host && connection.port && `:${connection.port}`} - + ); diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx index 863b9e1054..649b3dc74f 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Origin.tsx @@ -9,19 +9,18 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { StaticImage } from '@cloudbeaver/core-blocks'; +import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; +import { PlaceholderComponent, StaticImage } from '@cloudbeaver/core-blocks'; -import type { DatabaseConnection } from '../../../ConnectionsResource'; +import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService'; import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles'; -interface Props { - context: DatabaseConnection; -} - -export const Origin: React.FC = observer(function Origin({ context }) { - const isLocal = context.origin.type === 'local'; - const icon = context.origin.icon; - const title = context.origin.displayName; +export const Origin: PlaceholderComponent = observer(function Origin({ + connection, +}) { + const isLocal = connection.origin.type === AUTH_PROVIDER_LOCAL_ID; + const icon = connection.origin.icon; + const title = connection.origin.displayName; if (isLocal) { return null; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx index e3b1e322d9..702bcffcae 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/SSH.tsx @@ -9,21 +9,17 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { StaticImage, useMapResource } from '@cloudbeaver/core-blocks'; +import { PlaceholderComponent, StaticImage, useMapResource } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { NetworkHandlerResource, SSH_TUNNEL_ID } from '../../../../NetworkHandlerResource'; -import type { DatabaseConnection } from '../../../ConnectionsResource'; +import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService'; import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles'; -interface Props { - context: DatabaseConnection; -} - -export const SSH: React.FC = observer(function SSH({ context }) { +export const SSH: PlaceholderComponent = observer(function SSH({ connection }) { const translate = useTranslate(); const handler = useMapResource(NetworkHandlerResource, SSH_TUNNEL_ID); - const sshConfig = context.networkHandlersConfig.find(state => state.id === SSH_TUNNEL_ID); + const sshConfig = connection.networkHandlersConfig.find(state => state.id === SSH_TUNNEL_ID); if (!sshConfig || !sshConfig.enabled) { return null; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx index b3d56ae706..d5bb9a11a1 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionDetailsInfo/Template.tsx @@ -9,17 +9,13 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { StaticImage } from '@cloudbeaver/core-blocks'; +import { PlaceholderComponent, StaticImage } from '@cloudbeaver/core-blocks'; -import type { DatabaseConnection } from '../../../ConnectionsResource'; +import type { IConnectionDetailsPlaceholderProps } from '../../ConnectionsAdministrationService'; import { CONNECTION_DETAILS_STYLES } from './ConnectionDetailsStyles'; -interface Props { - context: DatabaseConnection; -} - -export const Template: React.FC = observer(function Template({ context }) { - if (!context.template) { +export const Template: PlaceholderComponent = observer(function Template({ connection }) { + if (!connection.template) { return null; } diff --git a/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx b/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx index bd9ad3173c..186852b28a 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/ConnectionForm.tsx @@ -111,7 +111,7 @@ export const ConnectionForm = observer(function ConnectionForm({ - + + + + ); +}); diff --git a/webapp/packages/core-ui/src/index.ts b/webapp/packages/core-ui/src/index.ts index 7c725d6bed..11001f83a2 100644 --- a/webapp/packages/core-ui/src/index.ts +++ b/webapp/packages/core-ui/src/index.ts @@ -1,2 +1,3 @@ export * from './Screens/AppScreen/NavigationService'; export * from './Screens/AppScreen/OptionsPanelService'; +export * from './AuthenticationProvider'; diff --git a/webapp/packages/core-utils/src/BuildVersion.ts b/webapp/packages/core-utils/src/BuildVersion.ts new file mode 100644 index 0000000000..2322d7c756 --- /dev/null +++ b/webapp/packages/core-utils/src/BuildVersion.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +declare const _VERSION_: string | undefined; + +export const BuildVersion = { + get version(): string | undefined { + return _VERSION_; + }, +}; diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index a7307525ed..41bc0758dc 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -1,3 +1,4 @@ +export * from './BuildVersion'; export * from './flat'; export * from './MetadataMap'; export * from './OrderedMap'; 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 20c76aed6e..af943e3123 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationInfoForm.tsx @@ -12,15 +12,16 @@ import styled from 'reshadow'; import { Group, GroupTitle, BASE_CONTAINERS_STYLES, InputFieldNew } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; -import type { ServerConfigInput } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; +import type { IServerConfigurationPageState } from '../IServerConfigurationPageState'; + interface Props { - serverConfig: ServerConfigInput; + state: IServerConfigurationPageState; } export const ServerConfigurationInfoForm: React.FC = observer(function ServerConfigurationInfoForm({ - serverConfig, + state, }) { const translate = useTranslate(); return styled(useStyles(BASE_CONTAINERS_STYLES))( @@ -29,7 +30,7 @@ export const ServerConfigurationInfoForm: React.FC = observer(function Se = observer(function Se (v ?? 1800000) / 1000 / 60} + mapValue={v => (v ?? 30) * 1000 * 60} required tiny > diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index 7a9efaee32..ed17437595 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -24,10 +24,6 @@ import { ServerConfigurationPageController } from './ServerConfigurationPageCont import { ServerConfigurationService } from './ServerConfigurationService'; const styles = css` - Loader { - height: 400px; - } - SubmittingForm { flex: 1; display: flex; @@ -35,10 +31,6 @@ const styles = css` flex-direction: column; } - FormBox { - flex: 0; - } - p { white-space: pre-wrap; line-height: 2; @@ -75,28 +67,25 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser )} - - - {() => styled(style)( - <> - - - {translate('administration_configuration_wizard_configuration_plugins')} - - - - - - - - )} - - + + {() => styled(style)( + + + + {translate('administration_configuration_wizard_configuration_plugins')} + + + + + + + + )} +
); diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 31e1763c88..305986484b 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -9,7 +9,6 @@ import { observable, makeObservable } from 'mobx'; import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { UsersResource } from '@cloudbeaver/core-authentication'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; @@ -20,8 +19,9 @@ import { GraphQLService } from '@cloudbeaver/core-sdk'; import type { IServerConfigurationPageState } from './IServerConfigurationPageState'; -export interface IConfigurationPlaceholderProps extends IServerConfigurationPageState { +export interface IConfigurationPlaceholderProps { configurationWizard: boolean; + state: IServerConfigurationPageState; } export interface IServerConfigSaveData { @@ -54,7 +54,6 @@ export class ServerConfigurationService { private readonly serverConfigResource: ServerConfigResource, private readonly graphQLService: GraphQLService, private readonly notificationService: NotificationService, - private readonly usersResource: UsersResource, ) { makeObservable(this, { state: observable, @@ -83,8 +82,6 @@ export class ServerConfigurationService { .before(this.prepareConfigTask) .addPostHandler(this.save); - this.prepareConfigTask.addHandler(this.prepareConfig); - this.validationTask .addHandler(this.validateForm) .addPostHandler(this.ensureValidation); @@ -95,18 +92,17 @@ export class ServerConfigurationService { } async loadConfig(): Promise { - let reload = false; - try { - const config = await this.serverConfigResource.load(); + let reload = false; this.state = this.administrationScreenService.getItemState( 'server-configuration', () => { reload = true; - return serverConfigStateContext(); + return this.state; }, - !config?.configurationMode + !this.administrationScreenService.isConfigurationMode ); + this.serverConfigResource.setDataUpdate(this.state.serverConfig); await this.loadConfigTask.execute({ state: this.state, @@ -145,25 +141,14 @@ export class ServerConfigurationService { return; } - if (config.configurationMode) { - data.state.serverConfig.serverName = 'CloudBeaver'; - data.state.serverConfig.sessionExpireTime = 30; + data.state.serverConfig.serverName = config.name || config.productInfo.name; + data.state.serverConfig.sessionExpireTime = config.sessionExpireTime; - data.state.serverConfig.adminCredentialsSaveEnabled = true; - data.state.serverConfig.publicCredentialsSaveEnabled = true; - data.state.serverConfig.customConnectionsEnabled = true; + data.state.serverConfig.adminCredentialsSaveEnabled = config.adminCredentialsSaveEnabled; + data.state.serverConfig.publicCredentialsSaveEnabled = config.publicCredentialsSaveEnabled; + data.state.serverConfig.customConnectionsEnabled = config.supportsCustomConnections; - data.state.navigatorConfig = { ...DEFAULT_NAVIGATOR_VIEW_SETTINGS }; - } else { - data.state.serverConfig.serverName = config.name; - data.state.serverConfig.sessionExpireTime = (config.sessionExpireTime ?? 1800000) / 1000 / 60; - - data.state.serverConfig.adminCredentialsSaveEnabled = config.adminCredentialsSaveEnabled; - data.state.serverConfig.publicCredentialsSaveEnabled = config.publicCredentialsSaveEnabled; - data.state.serverConfig.customConnectionsEnabled = config.supportsCustomConnections; - - data.state.navigatorConfig = { ...config.defaultNavigatorSettings }; - } + data.state.navigatorConfig = { ...config.defaultNavigatorSettings }; } catch (exception) { ExecutorInterrupter.interrupt(contexts); this.notificationService.logException(exception, 'Can\'t load server configuration'); @@ -178,35 +163,17 @@ export class ServerConfigurationService { }; } - private prepareConfig: IExecutorHandler = (data, contexts) => { - const state = contexts.getContext(serverConfigStateContext); - - state.serverConfig.serverName = data.state.serverConfig.serverName; - state.serverConfig.sessionExpireTime = (data.state.serverConfig.sessionExpireTime ?? 30) * 1000 * 60; - - state.serverConfig.adminCredentialsSaveEnabled = data.state.serverConfig.adminCredentialsSaveEnabled; - state.serverConfig.publicCredentialsSaveEnabled = data.state.serverConfig.publicCredentialsSaveEnabled; - state.serverConfig.customConnectionsEnabled = data.state.serverConfig.customConnectionsEnabled; - - state.navigatorConfig = { ...data.state.navigatorConfig }; - }; - private save: IExecutorHandler = async (data, contexts) => { const validation = contexts.getContext(serverConfigValidationContext); - const state = contexts.getContext(serverConfigStateContext); if (!validation.getState()) { return; } try { - await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: state.navigatorConfig }); + await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: data.state.navigatorConfig }); if (!data.configurationWizard || data.finish) { - await this.graphQLService.sdk.configureServer({ - configuration: state.serverConfig, - }); - await this.serverConfigResource.update(); - this.usersResource.refreshAllLazy(); + await this.serverConfigResource.save(); } } catch (exception) { this.notificationService.logException(exception, 'Can\'t save server configuration'); @@ -267,8 +234,6 @@ export function serverConfigValidationContext(): IValidationStatusContext { export function serverConfigStateContext(): IServerConfigurationPageState { return { navigatorConfig: { ...DEFAULT_NAVIGATOR_VIEW_SETTINGS }, - serverConfig: { - enabledAuthProviders: [], - }, + serverConfig: {}, }; } diff --git a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx index a7e3e5a71d..f2c971d052 100644 --- a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; import { AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; -import { BASE_CONTAINERS_STYLES, Container, Group, GroupTitle, Loader, PlaceholderComponent, SwitchNew, useDataResource } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, Container, Group, GroupTitle, Loader, PlaceholderComponent, SwitchNew, useMapResource } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration'; @@ -18,12 +18,13 @@ import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-adminis import { ServerConfigurationAdminForm } from './ServerConfigurationAdminForm'; export const AuthenticationProviders: PlaceholderComponent = observer(function AuthenticationProviders({ - serverConfig, + state: { serverConfig }, configurationWizard, }) { - const providers = useDataResource(AuthProvidersResource, undefined); + const providers = useMapResource(AuthProvidersResource, AuthProvidersResource.keyAll); const translate = useTranslate(); const styles = useStyles(BASE_CONTAINERS_STYLES); + const disabled = providers.data.length === 1 && !providers.resource.has(AUTH_PROVIDER_LOCAL_ID); return styled(styles)( @@ -58,7 +59,7 @@ export const AuthenticationProviders: PlaceholderComponent !disabled && styled(styles)( <> {translate('administration_configuration_wizard_configuration_authentication_provider')} - {providers.data.map(provider => ( + {providers.data.map(provider => provider && ( = async (data, contexts) => { - const providers = await this.authProvidersResource.load(); + const providers = await this.authProvidersResource.loadAll(); const disabled = providers.length === 1 && !this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); if (disabled) { - data.state.serverConfig.enabledAuthProviders = providers.map(provider => provider.id); + data.state.serverConfig.enabledAuthProviders = [...this.authProvidersResource.keys]; data.state.serverConfig.authenticationEnabled = true; } @@ -71,26 +71,20 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { }; private prepareConfig: IExecutorHandler = async (data, contexts) => { - const providers = await this.authProvidersResource.load(); + const providers = await this.authProvidersResource.loadAll(); const disabled = providers.length === 1 && !this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); - const state = contexts.getContext(serverConfigStateContext); - - state.serverConfig.anonymousAccessEnabled = data.state.serverConfig.anonymousAccessEnabled; - state.serverConfig.authenticationEnabled = data.state.serverConfig.authenticationEnabled; - state.serverConfig.enabledAuthProviders = data.state.serverConfig.enabledAuthProviders; - if (disabled) { - state.serverConfig.enabledAuthProviders = providers.map(provider => provider.id); - state.serverConfig.authenticationEnabled = true; + data.state.serverConfig.enabledAuthProviders = [...this.authProvidersResource.keys]; + data.state.serverConfig.authenticationEnabled = true; } if ( - data.configurationWizard - && state.serverConfig.enabledAuthProviders?.includes(AUTH_PROVIDER_LOCAL_ID) + !data.configurationWizard + || !data.state.serverConfig.enabledAuthProviders?.includes(AUTH_PROVIDER_LOCAL_ID) ) { - state.serverConfig.adminName = data.state.serverConfig.adminName; - state.serverConfig.adminPassword = data.state.serverConfig.adminPassword; + data.state.serverConfig.adminName = undefined; + data.state.serverConfig.adminPassword = undefined; } }; diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts index 44257bab9b..9b5b04f7aa 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts @@ -40,7 +40,7 @@ export class UsersAdministrationController implements IInitializableController { } get isLocalProviderAvailable(): boolean { - return this.authProvidersResource.data.some(({ id }) => id === AUTH_PROVIDER_LOCAL_ID); + return this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); } get isLoading(): boolean { @@ -65,7 +65,7 @@ export class UsersAdministrationController implements IInitializableController { } init(): void { - this.authProvidersResource.load(); + this.authProvidersResource.loadAll(); } update = async () => { diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index 467e583719..a43564f9c8 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -11,7 +11,8 @@ import { AppScreenService } from '@cloudbeaver/core-app'; import { AppAuthService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import type { IExecutorHandler } from '@cloudbeaver/core-executor'; +import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; +import { SessionDataResource } from '@cloudbeaver/core-root'; import { ScreenService } from '@cloudbeaver/core-routing'; import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; @@ -29,7 +30,8 @@ export class AuthenticationService extends Bootstrap { private notificationService: NotificationService, private readonly administrationScreenService: AdministrationScreenService, private readonly authProviderService: AuthProviderService, - private readonly authProvidersResource: AuthProvidersResource + private readonly authProvidersResource: AuthProvidersResource, + private readonly sessionDataResource: SessionDataResource, ) { super(); this.authPromise = null; @@ -73,11 +75,11 @@ export class AuthenticationService extends Bootstrap { } register(): void { - this.appAuthService.auth.addPostHandler(state => { - if (!state) { - this.requireAuthentication(); - } - }); + this.sessionDataResource.beforeLoad.addHandler( + ExecutorInterrupter.interrupter(() => this.appAuthService.isAuthNeeded()) + ); + this.sessionDataResource.beforeLoad.addPostHandler(() => { this.requireAuthentication(); }); + this.appScreenService.activation.addHandler(() => this.requireAuthentication()); this.administrationScreenService.ensurePermissions.addHandler(async () => { const userInfo = await this.userInfoResource.load(); @@ -99,7 +101,7 @@ export class AuthenticationService extends Bootstrap { return; } - await this.authProvidersResource.load(); + await this.authProvidersResource.loadAll(); await this.userInfoResource.load(); if (!this.authProvidersResource.has(data.subType ?? data.type)) { diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx index 08c85f1331..85ef3b6a85 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx @@ -9,10 +9,11 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; +import { UserInfoResource } from '@cloudbeaver/core-authentication'; import { SubmittingForm, ErrorMessage, TabsState, TabList, Tab, TabTitle, Loader } from '@cloudbeaver/core-blocks'; -import { useController } from '@cloudbeaver/core-di'; +import { useController, useService } from '@cloudbeaver/core-di'; import { CommonDialogWrapper, DialogComponent } from '@cloudbeaver/core-dialogs'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; @@ -77,6 +78,10 @@ const styles = composes( bottom: 0; padding: 8px 24px; } + auth-token-info-message { + composes: theme-typography--caption from global; + padding: 8px 24px; + } ` ); @@ -85,6 +90,7 @@ export const AuthDialog: DialogComponent = observer(functio options, rejectDialog, }) { + const userInfo = useService(UserInfoResource); const controller = useController(AuthDialogController, rejectDialog); const translate = useTranslate(); @@ -98,18 +104,24 @@ export const AuthDialog: DialogComponent = observer(functio - {controller.providers.map(provider => ( - controller.selectProvider(provider.id)} - > - {provider.label} - - ))} - + icon={controller.provider?.icon} + header={( + <> + {userInfo.data !== null && {translate('authentication_request_token')}} + {showTabs && ( + + {controller.providers.map(provider => ( + controller.selectProvider(provider.id)} + > + {provider.label} + + ))} + + )} + )} footer={( provider.id === providerId) || null; + this.provider = this.authProvidersResource.get(providerId) || null; this.credentials = {}; }; @@ -94,7 +92,7 @@ export class AuthDialogController implements IInitializableController, IDestruct private async loadProviders() { try { - await this.authProvidersResource.load(); + await this.authProvidersResource.loadAll(); if (this.providers.length > 0) { this.provider = this.providers[0]; } diff --git a/webapp/packages/plugin-authentication/src/locales/en.ts b/webapp/packages/plugin-authentication/src/locales/en.ts index 08a43fad59..b2ca22123f 100644 --- a/webapp/packages/plugin-authentication/src/locales/en.ts +++ b/webapp/packages/plugin-authentication/src/locales/en.ts @@ -2,6 +2,7 @@ export default [ ['authentication_login_dialog_title', 'Authentication'], ['authentication_login', 'Login'], ['authentication_logout', 'Logout'], + ['authentication_request_token', 'Additional authentication is required'], ['authentication_administration_user_connections_user_add', 'User Creation'], ['authentication_administration_user_connections_user_new', 'New user'], ['authentication_administration_user_connections_access_load_fail', 'User\'s granted connections loading failed'], diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts index a7faf70e95..a0823e9d72 100644 --- a/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts +++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts @@ -6,10 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { AppAuthService } from '@cloudbeaver/core-authentication'; import { Connection, ConnectionsResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, PermissionsService } from '@cloudbeaver/core-root'; +import { EPermission, PermissionsService, SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk'; @injectable() @@ -18,11 +17,11 @@ export class TemplateConnectionsResource extends CachedDataResource this.markOutdated()); - appAuthService.auth.addHandler(() => this.markOutdated()); + sessionDataResource.onDataOutdated.addHandler(() => this.markOutdated()); } isLoaded(): boolean { diff --git a/webapp/packages/plugin-data-export/src/Bootstrap.ts b/webapp/packages/plugin-data-export/src/Bootstrap.ts index de48a9905c..dcd7f37477 100644 --- a/webapp/packages/plugin-data-export/src/Bootstrap.ts +++ b/webapp/packages/plugin-data-export/src/Bootstrap.ts @@ -6,15 +6,19 @@ * you may not use this file except in compliance with the License. */ -import { injectable } from '@cloudbeaver/core-di'; +import { Bootstrap as B, injectable } from '@cloudbeaver/core-di'; import { DataExportMenuService } from './DataExportMenuService'; @injectable() -export class Bootstrap { - constructor(private dataExportMenuService: DataExportMenuService) { } +export class Bootstrap extends B { + constructor(private dataExportMenuService: DataExportMenuService) { + super(); + } - bootstrap() { + register(): void { this.dataExportMenuService.register(); } + + load(): void {} } diff --git a/webapp/packages/plugin-data-export/src/manifest.ts b/webapp/packages/plugin-data-export/src/manifest.ts index 735f61678f..f5c7b27694 100644 --- a/webapp/packages/plugin-data-export/src/manifest.ts +++ b/webapp/packages/plugin-data-export/src/manifest.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import type { IServiceInjector, PluginManifest } from '@cloudbeaver/core-di'; +import type { PluginManifest } from '@cloudbeaver/core-di'; import { Bootstrap } from './Bootstrap'; import { DataExportMenuService } from './DataExportMenuService'; @@ -21,16 +21,11 @@ export const manifest: PluginManifest = { }, providers: [ + Bootstrap, DataExportMenuService, DataExportService, DataExportProcessService, DataTransferProcessorsResource, LocaleService, ], - - async initialize(services: IServiceInjector) { - services - .resolveServiceByClass(Bootstrap) - .bootstrap(); - }, }; diff --git a/webapp/packages/plugin-ddl-viewer/src/DdlViewerBootstrap.ts b/webapp/packages/plugin-ddl-viewer/src/DdlViewerBootstrap.ts index 6b398bd808..df357133db 100644 --- a/webapp/packages/plugin-ddl-viewer/src/DdlViewerBootstrap.ts +++ b/webapp/packages/plugin-ddl-viewer/src/DdlViewerBootstrap.ts @@ -6,22 +6,27 @@ * you may not use this file except in compliance with the License. */ -import { injectable } from '@cloudbeaver/core-di'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ITabBuilder, ObjectFoldersService } from '@cloudbeaver/plugin-object-viewer'; import { DdlViewerTabService } from './DdlViewerTabService'; @injectable() -export class DdlViewerBootstrap { - constructor(private objectFoldersService: ObjectFoldersService, - private ddlViewerTabService: DdlViewerTabService) { +export class DdlViewerBootstrap extends Bootstrap { + constructor( + private objectFoldersService: ObjectFoldersService, + private ddlViewerTabService: DdlViewerTabService + ) { + super(); } - bootstrap() { + register(): void { const tabBuilder: ITabBuilder = { build: nodeId => this.ddlViewerTabService.buildTab(nodeId), }; this.objectFoldersService.registerTabConstructor(tabBuilder); } + + load(): void {} } diff --git a/webapp/packages/plugin-ddl-viewer/src/manifest.ts b/webapp/packages/plugin-ddl-viewer/src/manifest.ts index df6505eae5..8c024f01e5 100644 --- a/webapp/packages/plugin-ddl-viewer/src/manifest.ts +++ b/webapp/packages/plugin-ddl-viewer/src/manifest.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import type { IServiceInjector, PluginManifest } from '@cloudbeaver/core-di'; +import type { PluginManifest } from '@cloudbeaver/core-di'; import { DdlViewerBootstrap } from './DdlViewerBootstrap'; import { DdlViewerService } from './DdlViewerService'; @@ -18,12 +18,8 @@ export const manifest: PluginManifest = { }, providers: [ + DdlViewerBootstrap, DdlViewerTabService, DdlViewerService, ], - - initialize(services: IServiceInjector): void { - const service = services.resolveServiceByClass(DdlViewerBootstrap); - service.bootstrap(); - }, }; diff --git a/webapp/packages/plugin-object-viewer/src/ObjectViewerBootstrap.ts b/webapp/packages/plugin-object-viewer/src/ObjectViewerBootstrap.ts index 3f11d8829d..72bdb4794c 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectViewerBootstrap.ts +++ b/webapp/packages/plugin-object-viewer/src/ObjectViewerBootstrap.ts @@ -6,20 +6,24 @@ * you may not use this file except in compliance with the License. */ -import { injectable } from '@cloudbeaver/core-di'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ObjectPropertiesPageService } from './ObjectPropertiesPage/ObjectPropertiesPageService'; import { ObjectViewerTabService } from './ObjectViewerTabService'; @injectable() -export class ObjectViewerBootstrap { +export class ObjectViewerBootstrap extends Bootstrap { constructor( private objectViewerTabService: ObjectViewerTabService, private objectPropertiesPageService: ObjectPropertiesPageService - ) { } + ) { + super(); + } - bootstrap() { + register(): void { this.objectViewerTabService.registerTabHandler(); this.objectPropertiesPageService.registerDBObjectPage(); } + + load(): void {} } diff --git a/webapp/packages/plugin-object-viewer/src/manifest.ts b/webapp/packages/plugin-object-viewer/src/manifest.ts index 56dbca742a..a2073f1e7b 100644 --- a/webapp/packages/plugin-object-viewer/src/manifest.ts +++ b/webapp/packages/plugin-object-viewer/src/manifest.ts @@ -22,6 +22,7 @@ export const manifest: PluginManifest = { info: { name: 'Object Viewer Plugin' }, providers: [ + ObjectViewerBootstrap, ObjectPropertiesPageService, ObjectFoldersService, ObjectViewerTabService, @@ -31,11 +32,4 @@ export const manifest: PluginManifest = { DBObjectPageService, LocaleService, ], - - async initialize(injector: IServiceInjector) { - injector - .resolveServiceByClass(ObjectViewerBootstrap) - .bootstrap(); - }, - }; diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts index ecc07d7396..18432bfd20 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorBootstrap.ts @@ -15,7 +15,7 @@ import { isObjectCatalogProvider, isObjectSchemaProvider } from '@cloudbeaver/core-app'; import { isConnectionProvider } from '@cloudbeaver/core-connections'; -import { injectable } from '@cloudbeaver/core-di'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ContextMenuService, IMenuContext } from '@cloudbeaver/core-dialogs'; import { ExtensionUtils } from '@cloudbeaver/core-extensions'; import { ActiveViewService } from '@cloudbeaver/core-view'; @@ -23,16 +23,18 @@ import { ActiveViewService } from '@cloudbeaver/core-view'; import { SqlEditorNavigatorService } from './SqlEditorNavigatorService'; @injectable() -export class SqlEditorBootstrap { +export class SqlEditorBootstrap extends Bootstrap { constructor( private mainMenuService: MainMenuService, private contextMenuService: ContextMenuService, private sqlEditorNavigatorService: SqlEditorNavigatorService, private connectionSchemaManagerService: ConnectionSchemaManagerService, private activeViewService: ActiveViewService - ) { } + ) { + super(); + } - async bootstrap() { + register(): void { this.mainMenuService.registerRootItem( { id: 'sql-editor', @@ -58,6 +60,8 @@ export class SqlEditorBootstrap { }); } + load(): void {} + private isSQLEntryDisabled() { const activeView = this.activeViewService.view; if (activeView) { diff --git a/webapp/packages/plugin-sql-editor/src/manifest.ts b/webapp/packages/plugin-sql-editor/src/manifest.ts index 4a851b7d97..f23d088ece 100644 --- a/webapp/packages/plugin-sql-editor/src/manifest.ts +++ b/webapp/packages/plugin-sql-editor/src/manifest.ts @@ -23,6 +23,7 @@ export const sqlEditorPluginManifest: PluginManifest = { }, providers: [ + SqlEditorBootstrap, SqlDialectInfoService, SqlEditorTabService, SqlResultTabsService, @@ -31,8 +32,4 @@ export const sqlEditorPluginManifest: PluginManifest = { SqlEditorNavigatorService, LocaleService, ], - - initialize(services): void { - services.resolveServiceByClass(SqlEditorBootstrap).bootstrap(); - }, }; From 008bb7590de6bced8995d192917139ddefbae927 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 29 Mar 2021 11:26:48 +0300 Subject: [PATCH 37/75] feat(plugin-gis-viewer): CB-664 changed invalidate size logic --- .../plugin-gis-viewer/src/LeafletMap.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx index 04ee532e77..4fccdbd03c 100644 --- a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx +++ b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx @@ -141,18 +141,12 @@ export const LeafletMap: React.FC = function LeafletMap({ geoJSON, getAss }, [getAssociatedValues]); useEffect(() => { - if (geoJSONLayerRef) { + if (geoJSONLayerRef && mapRef) { geoJSONLayerRef.clearLayers(); for (let i = 0; i < geoJSON.length; i++) { geoJSONLayerRef.addData(geoJSON[i]); } - } - }, [geoJSON, geoJSONLayerRef]); - - useEffect(() => { - if (mapRef && geoJSONLayerRef) { - mapRef.invalidateSize(); const bounds = geoJSONLayerRef.getBounds(); @@ -172,7 +166,13 @@ export const LeafletMap: React.FC = function LeafletMap({ geoJSON, getAss mapRef.fitBounds(bounds); } } - }, [splitContext.isResizing, splitContext.mode, mapRef, geoJSONLayerRef, crs, geoJSON]); + }, [geoJSON, geoJSONLayerRef, crs, mapRef]); + + useEffect(() => { + if (mapRef) { + mapRef.invalidateSize(); + } + }, [splitContext.isResizing, splitContext.mode, mapRef]); return styled(styles)( From b5205cc6f017030d2b98e5070e797ada15029534 Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 29 Mar 2021 13:15:34 +0300 Subject: [PATCH 38/75] chore: update version 20.0.6 --- webapp/packages/product-default/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/product-default/package.json b/webapp/packages/product-default/package.json index 8752269a3e..bc6f8be396 100644 --- a/webapp/packages/product-default/package.json +++ b/webapp/packages/product-default/package.json @@ -1,7 +1,7 @@ { "name": "@cloudbeaver/product-default", "sideEffects": false, - "version": "20.0.5", + "version": "20.0.6", "description": "", "license": "Apache-2.0", "main": "lib/index.js", From a38023730ed0f53bf027f94232e0640fc3623fab Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 29 Mar 2021 16:08:14 +0300 Subject: [PATCH 39/75] feat(plugin-gis-viewer): CB-664 add async modules import, remove terraformer-wkt-parser --- .../configs/webpack.product.config.js | 26 +++++++------- .../packages/plugin-gis-viewer/package.json | 4 +-- .../src/GISValuePresentation.tsx | 29 +++++++++++++--- .../src/GISValuePresentationLoader.tsx | 34 +++++++++++++++++++ .../plugin-gis-viewer/src/GISViewer.tsx | 19 +++++++++++ .../src/GISViewerBootstrap.ts | 4 +-- .../plugin-gis-viewer/src/LeafletMap.tsx | 2 +- .../src/terraformer-wkt.d.ts | 15 ++++++++ webapp/yarn.lock | 27 ++++----------- 9 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 webapp/packages/plugin-gis-viewer/src/GISValuePresentationLoader.tsx create mode 100644 webapp/packages/plugin-gis-viewer/src/GISViewer.tsx create mode 100644 webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts diff --git a/webapp/packages/core-cli/configs/webpack.product.config.js b/webapp/packages/core-cli/configs/webpack.product.config.js index 1932be88e6..330d76569f 100644 --- a/webapp/packages/core-cli/configs/webpack.product.config.js +++ b/webapp/packages/core-cli/configs/webpack.product.config.js @@ -1,12 +1,12 @@ -const { getAssets, withTimestamp } = require('./webpack.product.utils'); +const { resolve } = require('path') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const TerserPlugin = require('terser-webpack-plugin') +const webpack = require('webpack') const { merge } = require('webpack-merge'); -const webpack = require('webpack'); -const { resolve } = require('path'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const TerserPlugin = require("terser-webpack-plugin"); -const commonConfig = require('./webpack.config.js'); +const commonConfig = require('./webpack.config.js') +const { getAssets, withTimestamp } = require('./webpack.product.utils') const main = resolve('src/index.ts'); const outputDir = resolve('lib'); @@ -31,7 +31,7 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { cacheGroups: { vendor: { // TODO: we need another way to detect libraries to exclude - test: /[\\/]node_modules[\\/](?!(@ag-grid|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, + test: /[\\/]node_modules[\\/](?!(leaflet|react-leaflet|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, name: 'vendors', chunks: 'all', }, @@ -40,7 +40,7 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { usedExports: true, sideEffects: true, concatenateModules: true, - + minimizer: [new TerserPlugin({ extractComments: /Copyright \(C\)/i, })], @@ -52,10 +52,10 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { new webpack.DefinePlugin({ _VERSION_: JSON.stringify(timestampVersion), }), - new HtmlWebpackPlugin({ - template: resolve('src/index.html.ejs'), - inject: 'body', - version: timestampVersion + new HtmlWebpackPlugin({ + template: resolve('src/index.html.ejs'), + inject: 'body', + version: timestampVersion, }), ], }); diff --git a/webapp/packages/plugin-gis-viewer/package.json b/webapp/packages/plugin-gis-viewer/package.json index 7cac418ca7..86c811d289 100644 --- a/webapp/packages/plugin-gis-viewer/package.json +++ b/webapp/packages/plugin-gis-viewer/package.json @@ -38,9 +38,9 @@ "rxjs": "^6.x.x" }, "dependencies": { + "@terraformer/wkt": "^2.0.7", "leaflet": "^1.7.1", - "react-leaflet": "^3.1.0", - "terraformer-wkt-parser": "^1.2.1" + "react-leaflet": "^3.1.0" }, "devDependencies": { "@types/react-leaflet": "^2.8.1" diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index 3b97037d35..5f90c9a24b 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -6,18 +6,26 @@ * you may not use this file except in compliance with the License. */ +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// + import { observer } from 'mobx-react-lite'; import { useCallback, useMemo } from 'react'; -import wkt from 'terraformer-wkt-parser'; -import { TabContainerPanelComponent, TextPlaceholder } from '@cloudbeaver/core-blocks'; +import { TextPlaceholder } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { IDataValuePanelProps, IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; +import { wktToGeoJSON } from '@terraformer/wkt'; import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap'; import { ResultSetGISAction } from './ResultSetGISAction'; -export const GISValuePresentation: TabContainerPanelComponent> = observer(function GISValuePresentation({ +interface Props { + model: IDatabaseDataModel; + resultIndex: number; +} + +export const GISValuePresentation: React.FC = observer(function GISValuePresentation({ model, resultIndex, }) { @@ -44,7 +52,18 @@ export const GISValuePresentation: TabContainerPanelComponent; + resultIndex: number; +} + +export const GISValuePresentationLoader: React.FC = function GISValuePresentationLoader({ model, resultIndex }) { + return ( + } + > + {({ GISValuePresentation }) => ( + + )} + + ); +} +; diff --git a/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx new file mode 100644 index 0000000000..9d7209610b --- /dev/null +++ b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx @@ -0,0 +1,19 @@ +/* + * 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 type { TabContainerPanelComponent } from '@cloudbeaver/core-blocks'; +import type { IDataValuePanelProps, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; + +import { GISValuePresentationLoader } from './GISValuePresentationLoader'; + +export const GISViewer: TabContainerPanelComponent> = function GISViewer({ model, resultIndex }) { + return ( + + ); +} +; diff --git a/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts b/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts index 64be251a3e..7bead27928 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts +++ b/webapp/packages/plugin-gis-viewer/src/GISViewerBootstrap.ts @@ -10,7 +10,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { DataValuePanelService, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; -import { GISValuePresentation } from './GISValuePresentation'; +import { GISViewer } from './GISViewer'; import { ResultSetGISAction } from './ResultSetGISAction'; @injectable() @@ -25,7 +25,7 @@ export class GISViewerBootstrap extends Bootstrap { options: { dataFormat: [ResultDataFormat.Resultset] }, name: 'gis_presentation_title', order: 10, - panel: () => GISValuePresentation, + panel: () => GISViewer, isHidden: (_, context) => { if (!context || !context.model.source.hasResult(context.resultIndex)) { return true; diff --git a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx index 4fccdbd03c..f7beaf47d7 100644 --- a/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx +++ b/webapp/packages/plugin-gis-viewer/src/LeafletMap.tsx @@ -150,7 +150,7 @@ export const LeafletMap: React.FC = function LeafletMap({ geoJSON, getAss const bounds = geoJSONLayerRef.getBounds(); - if (Object.keys(bounds).length <= 0) { + if (Object.keys(bounds).length === 0) { return; } diff --git a/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts b/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts new file mode 100644 index 0000000000..6aee3c7dfd --- /dev/null +++ b/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +declare module '@terraformer/wkt' { + import * as terraformer from '@terraformer/wkt'; + + export function wktToGeoJSON(wkt: string): GeoJSON.GeometryObject; + export function GeoJSONToWkt(geoJSON: GeoJSON.GeometryObject): string; + +} diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 5a400866d4..1f2c321629 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2712,6 +2712,11 @@ dependencies: defer-to-connect "^1.0.1" +"@terraformer/wkt@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@terraformer/wkt/-/wkt-2.0.7.tgz#970e7f72f361194fee4daca7738b6f15421d29d3" + integrity sha512-AOQHnQ5eqs5xWtIiX+ys/Xg80aYouTtBHiQDxRWFUhlmOBzrg7TfjrpC9gNPx8NP7XvuTzSwkARwCWCjOtDiWA== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2750,16 +2755,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== -"@types/geojson@*", "@types/geojson@^7946.0.0 || ^1.0.0": +"@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" integrity sha512-wE2v81i4C4Ol09RtsWFAqg3BUitWbHSpSlIo+bNdsCJijO9sjme+zm+73ZMCa/qMC8UEERxzGbvmr1cffo2SiQ== -"@types/geojson@^1.0.0": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.6.tgz#3e02972728c69248c2af08d60a48cbb8680fffdf" - integrity sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w== - "@types/glob@^7.1.1": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" @@ -12825,21 +12825,6 @@ temp-write@^3.4.0: temp-dir "^1.0.0" uuid "^3.0.1" -terraformer-wkt-parser@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/terraformer-wkt-parser/-/terraformer-wkt-parser-1.2.1.tgz#8041e2aeb0c9f2b4cbbec8ec2c5c00c45ddfee02" - integrity sha512-+CJyNLWb3lJ9RsZMTM66BY0MT3yIo4l4l22Jd9CrZuwzk54fsu4Sc7zejuS9fCITTuTQy3p06d4MZMVI7v5wSg== - dependencies: - "@types/geojson" "^1.0.0" - terraformer "~1.0.5" - -terraformer@~1.0.5: - version "1.0.12" - resolved "https://registry.yarnpkg.com/terraformer/-/terraformer-1.0.12.tgz#39e08f9c753606421acce02e122440c72dfa12d3" - integrity sha512-MokUp0+MFal4CmJDVL6VAO1bKegeXcBM2RnPVfqcFIp2IIv8EbPAjG0j/vEy/vuKB8NVMMSF2vfpVS/QLe4DBg== - optionalDependencies: - "@types/geojson" "^7946.0.0 || ^1.0.0" - terser-webpack-plugin@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" From 35fac7bad060f0ffe568dbf53039132f86fb0c8d Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Mon, 29 Mar 2021 16:19:42 +0300 Subject: [PATCH 40/75] CB-797 User/providers linking API --- .../io/cloudbeaver/DBWSecurityController.java | 5 ++++ .../server/CBSecurityController.java | 23 ++++++++++++++++ .../service/WebServiceBindingBase.java | 9 +++++-- .../schema/service.auth.graphqls | 6 +++-- .../service/auth/DBWServiceAuth.java | 6 ++++- .../service/auth/WebServiceBindingAuth.java | 4 ++- .../cloudbeaver/service/auth/WebUserInfo.java | 18 +++++++++++++ .../service/auth/impl/WebServiceAuthImpl.java | 27 +++++++++++++++---- 8 files changed, 87 insertions(+), 11 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java index 0c0f803238..e7babd2ae2 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java @@ -70,6 +70,11 @@ public interface DBWSecurityController { */ Map getUserCredentials(String userId, WebAuthProviderDescriptor authProvider) throws DBCException; + /** + * Returns list of auth provider IDs associated with this user + */ + String[] getUserLinkedProviders(String userId) throws DBCException; + /////////////////////////////////////////// // Roles diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java index 39741aae0c..1d38f60dbd 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java @@ -373,6 +373,29 @@ class CBSecurityController implements DBWSecurityController { } } + @Override + public String[] getUserLinkedProviders(String userId) throws DBCException { + try (Connection dbCon = database.openConnection()) { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT DISTINCT PROVIDER_ID FROM CB_USER_CREDENTIALS\n" + + "WHERE USER_ID=?")) { + dbStat.setString(1, userId); + + try (ResultSet dbResult = dbStat.executeQuery()) { + List providerIds = new ArrayList<>(); + + while (dbResult.next()) { + providerIds.add(dbResult.getString(1)); + } + + return providerIds.toArray(new String[0]); + } + } + } catch (SQLException e) { + throw new DBCException("Error saving role in database", e); + } + } + /////////////////////////////////////////// // Roles diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index 547a746ab1..de1c9ca0ab 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -26,6 +26,7 @@ import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.server.graphql.GraphQLEndpoint; import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.Log; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; @@ -45,6 +46,8 @@ import java.util.Set; */ public abstract class WebServiceBindingBase implements DBWServiceBindingGraphQL { + private static final Log log = Log.getLog(WebServiceBindingBase.class); + private final Class apiInterface; private final API_TYPE serviceImpl; private final String schemaFileName; @@ -132,7 +135,7 @@ public abstract class WebServiceBindingBase impleme try { WebAction webAction = method.getAnnotation(WebAction.class); if (webAction != null) { - checkPermissions(webAction); + checkPermissions(method, webAction); } return method.invoke(impl, args); } catch (InvocationTargetException e) { @@ -140,7 +143,7 @@ public abstract class WebServiceBindingBase impleme } } - private void checkPermissions(WebAction webAction) throws DBWebException { + private void checkPermissions(@NotNull Method method, @NotNull WebAction webAction) throws DBWebException { String[] reqPermissions = webAction.requirePermissions(); if (reqPermissions.length == 0) { return; @@ -153,6 +156,7 @@ public abstract class WebServiceBindingBase impleme if (!application.isConfigurationMode()) { Set sessionPermissions = session.getSessionPermissions(); if (CommonUtils.isEmpty(sessionPermissions)) { + log.debug("Anonymous access to " + method.getName() + " restricted"); throw new DBWebExceptionAccessDenied("Anonymous access restricted"); } @@ -166,6 +170,7 @@ public abstract class WebServiceBindingBase impleme // Check permissions for (String rp : reqPermissions) { if (!sessionPermissions.contains(rp)) { + log.debug("Access to " + method.getName() + " denied for " + session.getUser()); throw new DBWebExceptionAccessDenied("Access denied"); } } diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index f3df97abc6..68f4637954 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -58,12 +58,14 @@ type UserInfo { displayName: String authTokens: [UserAuthToken!]! + + linkedAuthProviders: [String!]! } extend type Query { - # Authorize user using specified auth provider - authLogin(provider: ID!, credentials: Object!): UserAuthToken! + # Authorize user using specified auth provider. If linkUser=true then associates new + authLogin(provider: ID!, credentials: Object!, linkUser: Boolean): UserAuthToken! # Logouts user. If provider not specified then all authorizations are revoked from session. authLogout(provider: ID): Boolean diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java index ab8fd29221..f2131c58f4 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/DBWServiceAuth.java @@ -33,7 +33,11 @@ import java.util.Map; public interface DBWServiceAuth extends DBWService { @WebAction(requirePermissions = {} ) - WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map credentials) throws DBWebException; + WebAuthInfo authLogin( + @NotNull WebSession webSession, + @NotNull String providerId, + @NotNull Map credentials, + boolean linkWithActiveUser) throws DBWebException; @WebAction(requirePermissions = {} ) void authLogout(@NotNull WebSession webSession, @Nullable String providerId) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java index 99b1e9c86f..4a98cd5dd7 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebServiceBindingAuth.java @@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException; import io.cloudbeaver.service.DBWBindingContext; import io.cloudbeaver.service.WebServiceBindingBase; import io.cloudbeaver.service.auth.impl.WebServiceAuthImpl; +import org.jkiss.utils.CommonUtils; /** * Web service implementation @@ -38,7 +39,8 @@ public class WebServiceBindingAuth extends WebServiceBindingBase .dataFetcher("authLogin", env -> getService(env).authLogin( getWebSession(env, false), env.getArgument("provider"), - env.getArgument("credentials"))) + env.getArgument("credentials"), + CommonUtils.toBoolean(env.getArgument("linkUser")))) .dataFetcher("authLogout", env -> { getService(env).authLogout(getWebSession(env), env.getArgument("provider")); return true; diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java index e52c27b895..3aaf40c243 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java @@ -16,11 +16,15 @@ */ package io.cloudbeaver.service.auth; +import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.model.user.WebUser; +import io.cloudbeaver.server.CBApplication; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.meta.Property; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -33,6 +37,7 @@ public class WebUserInfo { private final WebSession session; private final WebUser user; + private String[] linkedProviders; public WebUserInfo(WebSession session, WebUser user) { this.session = session; @@ -56,4 +61,17 @@ public class WebUserInfo { .collect(Collectors.toList()); } + @Property + public List getLinkedAuthProviders() throws DBWebException { + if (linkedProviders == null) { + try { + linkedProviders = CBApplication.getInstance().getSecurityController() + .getUserLinkedProviders(session.getUser().getUserId()); + } catch (DBCException e) { + throw new DBWebException("Error reading user linked providers"); + } + } + return Arrays.asList(linkedProviders); + } + } diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index e320e73a2d..a2dea32a28 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -48,7 +48,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth { private static final Log log = Log.getLog(WebServiceAuthImpl.class); @Override - public WebAuthInfo authLogin(@NotNull WebSession webSession, @NotNull String providerId, @NotNull Map authParameters) throws DBWebException { + public WebAuthInfo authLogin( + @NotNull WebSession webSession, + @NotNull String providerId, + @NotNull Map authParameters, + boolean linkWithActiveUser) throws DBWebException { DBWSecurityController securityController = CBPlatform.getInstance().getApplication().getSecurityController(); if (CommonUtils.isEmpty(providerId)) { @@ -56,12 +60,20 @@ public class WebServiceAuthImpl implements DBWServiceAuth { } boolean configMode = CBApplication.getInstance().isConfigurationMode(); + // Check enabled auth providers + boolean providerEnabled = true; + String[] enabledAuthProviders = CBApplication.getInstance().getAppConfiguration().getEnabledAuthProviders(); + if (enabledAuthProviders != null && !ArrayUtils.contains(enabledAuthProviders, providerId)) { + providerEnabled = false; + } if (configMode || webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) { - // Admin can authorize in any providers + // 1. Admin can authorize in any providers + // 2. When it authorizes in non-local provider for the first time we force linkUser flag + if (!providerEnabled && webSession.getUser() != null) { + linkWithActiveUser = true; + } } else { - // Check enabled auth providers - String[] enabledAuthProviders = CBApplication.getInstance().getAppConfiguration().getEnabledAuthProviders(); - if (enabledAuthProviders != null && !ArrayUtils.contains(enabledAuthProviders, providerId)) { + if (!providerEnabled) { throw new DBWebException("Authentication provider '" + providerId + "' is disabled"); } } @@ -125,6 +137,11 @@ public class WebServiceAuthImpl implements DBWServiceAuth { userId); } } + } else { + // We may need to associate new credentials with active user + if (linkWithActiveUser) { + securityController.setUserCredentials(userId, authProvider, userCredentials); + } } } From 1a74c59adb1aa127de8c1ccb5fa83558b9953170 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 29 Mar 2021 16:26:11 +0300 Subject: [PATCH 41/75] feat(plugin-gis-viewer): CB-664 exclude @terraformer from vendor --- .../configs/webpack.product.config.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/webapp/packages/core-cli/configs/webpack.product.config.js b/webapp/packages/core-cli/configs/webpack.product.config.js index 330d76569f..05f2621a36 100644 --- a/webapp/packages/core-cli/configs/webpack.product.config.js +++ b/webapp/packages/core-cli/configs/webpack.product.config.js @@ -1,18 +1,18 @@ -const { resolve } = require('path') -const HtmlWebpackPlugin = require('html-webpack-plugin') -const CopyWebpackPlugin = require('copy-webpack-plugin') -const TerserPlugin = require('terser-webpack-plugin') -const webpack = require('webpack') -const { merge } = require('webpack-merge'); +const { resolve } = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const webpack = require('webpack'); +const { merge } = require('webpack-merge') -const commonConfig = require('./webpack.config.js') -const { getAssets, withTimestamp } = require('./webpack.product.utils') +const commonConfig = require('./webpack.config.js'); +const { getAssets, withTimestamp } = require('./webpack.product.utils'); -const main = resolve('src/index.ts'); -const outputDir = resolve('lib'); -const package = require(resolve('package.json')) +const main = resolve('src/index.ts') +const outputDir = resolve('lib') +const package = require(resolve('package.json')); -const timestampVersion = withTimestamp(package.version); +const timestampVersion = withTimestamp(package.version) module.exports = (env, argv) => merge(commonConfig(env, argv), { entry: main, @@ -31,7 +31,7 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { cacheGroups: { vendor: { // TODO: we need another way to detect libraries to exclude - test: /[\\/]node_modules[\\/](?!(leaflet|react-leaflet|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, + test: /[\\/]node_modules[\\/](?!(leaflet|react-leaflet|@terraformer|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, name: 'vendors', chunks: 'all', }, @@ -58,4 +58,4 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { version: timestampVersion, }), ], -}); +}) From 4409e722b007129088b96163cb1fd454cf84d7f5 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 29 Mar 2021 19:18:43 +0300 Subject: [PATCH 42/75] fix(core-connections): CB-849, CB-848, CB-852 ui text changes --- .../Options/ConnectionOptionsTabService.ts | 12 ++++++++---- .../core-connections/src/ConnectionForm/SSH/SSH.tsx | 2 +- .../src/DatabaseAuthDialog/SSHAuthForm.tsx | 2 +- webapp/packages/core-connections/src/locales/en.ts | 1 - webapp/packages/core-connections/src/locales/ru.ts | 1 - .../src/ConnectionDialog/ConnectionController.ts | 4 ++-- .../plugin-product/src/ProductInfoDialog.tsx | 2 +- 7 files changed, 13 insertions(+), 11 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index d49249cd0c..beef2ff00f 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -71,11 +71,13 @@ export class ConnectionOptionsTabService extends Bootstrap { if (submitType === 'submit') { if (options.mode === 'edit') { const connection = await this.connectionsResource.update(config.connectionId!, config); - status.info(`Connection ${connection.name} updated`); + status.info('Connection updated'); + status.info(`Connection name: ${connection.name}`); } else { const connection = await this.connectionsResource.create(config); config.connectionId = connection.id; - status.info(`Connection ${connection.name} created`); + status.info('Connection created'); + status.info(`Connection name: ${connection.name}`); } } else { const info = await this.connectionsResource.test(config); @@ -88,11 +90,13 @@ export class ConnectionOptionsTabService extends Bootstrap { if (submitType === 'submit') { if (options.mode === 'edit') { const connection = await this.connectionInfoResource.update(config); - status.info(`Connection ${connection.name} updated`); + status.info('Connection updated'); + status.info(`Connection name: ${connection.name}`); } else { const connection = await this.connectionInfoResource.createConnection(config); config.connectionId = connection.id; - status.info(`Connection ${connection.name} created`); + status.info('Connection created'); + status.info(`Connection name: ${connection.name}`); } } else { const info = await this.connectionInfoResource.testConnection(config); diff --git a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx index fe704103ee..c739855979 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx @@ -159,7 +159,7 @@ export const SSH: TabContainerPanelComponent = observer value={SSH_TUNNEL_ID + ' savePassword'} state={state} disabled={disabled || !enabled || form.form.readonly} - >{translate('connections_network_handler_ssh_tunnel_save_password')} + >{translate('connections_connection_edit_save_credentials')} )} diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx index 16e52cd3a6..72c9ecfb56 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx @@ -79,7 +79,7 @@ export const SSHAuthForm: React.FC = observer(function SSHAuthForm({ name="savePassword" value={sshHandlerId + ' savePassword'} state={state} - label={translate('connections_network_handler_ssh_tunnel_save_password')} + label={translate('connections_connection_edit_save_credentials')} disabled={disabled} /> diff --git a/webapp/packages/core-connections/src/locales/en.ts b/webapp/packages/core-connections/src/locales/en.ts index 842f3aa5fb..e06144e497 100644 --- a/webapp/packages/core-connections/src/locales/en.ts +++ b/webapp/packages/core-connections/src/locales/en.ts @@ -42,7 +42,6 @@ export default [ ['connections_network_handler_ssh_tunnel_port', 'Port'], ['connections_network_handler_ssh_tunnel_user', 'User'], ['connections_network_handler_ssh_tunnel_password', 'Password'], - ['connections_network_handler_ssh_tunnel_save_password', 'Save Password'], ['connections_driver_search_placeholder', 'Type driver name...'], ['connections_not_found', 'No database connections were found'], ]; diff --git a/webapp/packages/core-connections/src/locales/ru.ts b/webapp/packages/core-connections/src/locales/ru.ts index 0447b919b2..4a772b878c 100644 --- a/webapp/packages/core-connections/src/locales/ru.ts +++ b/webapp/packages/core-connections/src/locales/ru.ts @@ -41,7 +41,6 @@ export default [ ['connections_network_handler_ssh_tunnel_port', 'Порт'], ['connections_network_handler_ssh_tunnel_user', 'Пользователь'], ['connections_network_handler_ssh_tunnel_password', 'Пароль'], - ['connections_network_handler_ssh_tunnel_save_password', 'Сохранить пароль'], ['connections_driver_search_placeholder', 'Введите название драйвера...'], ['connections_not_found', 'Подключения к базам данных не найдены'], ]; diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index aa4d40c714..4f255ada2a 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -34,7 +34,7 @@ export interface IConnectionController { @injectable() export class ConnectionController -implements IInitializableController, IDestructibleController, IConnectionController { + implements IInitializableController, IDestructibleController, IConnectionController { step = ConnectionStep.ConnectionTemplateSelect; isLoading = true; isConnecting = false; @@ -119,7 +119,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl try { await this.connectionInfoResource.init(this.getConfig(connection.id)); - this.notificationService.logSuccess({ title: `Connection ${connection.name} established` }); + this.notificationService.logSuccess({ title: 'Connection is established', message: `Connection name: ${connection.name}` }); this.onClose(); } catch (exception) { this.showError(exception, 'Failed to establish connection'); diff --git a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx index 9db6af2478..0190a6485f 100644 --- a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx +++ b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx @@ -59,7 +59,7 @@ export const ProductInfoDialog: React.FC> = observer( footer={( )} From ca36d09a0602720ba76356c0803fbbc71ed76387 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Mon, 29 Mar 2021 21:43:00 +0300 Subject: [PATCH 43/75] Delimiter detection fix --- .../src/io/cloudbeaver/service/sql/WebSQLDialectInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDialectInfo.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDialectInfo.java index 1ad7e7e6a7..18696a03d2 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDialectInfo.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDialectInfo.java @@ -19,6 +19,7 @@ package io.cloudbeaver.service.sql; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.sql.SQLDialect; +import org.jkiss.dbeaver.model.sql.SQLUtils; import org.jkiss.utils.Pair; /** @@ -77,7 +78,7 @@ public class WebSQLDialectInfo { } public String getScriptDelimiter() { - return dialect.getScriptDelimiter(); + return SQLUtils.getDefaultScriptDelimiter(dialect); } } From 2cf70ff2d5230d666b7d3602dc1c639e1769e2df Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 29 Mar 2021 23:31:25 +0300 Subject: [PATCH 44/75] fix(core-connections): CB-849, CB-848, CB-852 remove "Connection name: " --- .../ConnectionForm/Options/ConnectionOptionsTabService.ts | 8 ++++---- .../src/ConnectionDialog/ConnectionController.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index beef2ff00f..d7aefb3cb2 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -72,12 +72,12 @@ export class ConnectionOptionsTabService extends Bootstrap { if (options.mode === 'edit') { const connection = await this.connectionsResource.update(config.connectionId!, config); status.info('Connection updated'); - status.info(`Connection name: ${connection.name}`); + status.info(connection.name); } else { const connection = await this.connectionsResource.create(config); config.connectionId = connection.id; status.info('Connection created'); - status.info(`Connection name: ${connection.name}`); + status.info(connection.name); } } else { const info = await this.connectionsResource.test(config); @@ -91,12 +91,12 @@ export class ConnectionOptionsTabService extends Bootstrap { if (options.mode === 'edit') { const connection = await this.connectionInfoResource.update(config); status.info('Connection updated'); - status.info(`Connection name: ${connection.name}`); + status.info(connection.name); } else { const connection = await this.connectionInfoResource.createConnection(config); config.connectionId = connection.id; status.info('Connection created'); - status.info(`Connection name: ${connection.name}`); + status.info(connection.name); } } else { const info = await this.connectionInfoResource.testConnection(config); diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index 4f255ada2a..ae33144f00 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -119,7 +119,7 @@ export class ConnectionController try { await this.connectionInfoResource.init(this.getConfig(connection.id)); - this.notificationService.logSuccess({ title: 'Connection is established', message: `Connection name: ${connection.name}` }); + this.notificationService.logSuccess({ title: 'Connection is established', message: connection.name }); this.onClose(); } catch (exception) { this.showError(exception, 'Failed to establish connection'); From 061434324889112b818cd3b0be3c2a55fc3c705e Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 30 Mar 2021 12:05:00 +0300 Subject: [PATCH 45/75] feat(plugin-gis-viewer): CB-664 remove extra GISPresentation loader component --- .../src/GISValuePresentation.tsx | 11 ++---- .../src/GISValuePresentationLoader.tsx | 34 ------------------- .../plugin-gis-viewer/src/GISViewer.tsx | 15 ++++++-- 3 files changed, 15 insertions(+), 45 deletions(-) delete mode 100644 webapp/packages/plugin-gis-viewer/src/GISValuePresentationLoader.tsx diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index 5f90c9a24b..c47b46fb13 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -52,19 +52,12 @@ export const GISValuePresentation: React.FC = observer(function GISValueP continue; } - let parsedCellValue: GeoJSON.GeometryObject | null = null; - try { - parsedCellValue = wktToGeoJSON(cellValue.mapText || cellValue.text); + const parsedCellValue = wktToGeoJSON(cellValue.mapText || cellValue.text); + result.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } }); } catch { console.error(`Failed to parse ${cellValue.mapText || cellValue.text} value`); } - - if (!parsedCellValue) { - continue; - } - - result.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } }); } return result; diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentationLoader.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentationLoader.tsx deleted file mode 100644 index 5a30d4c653..0000000000 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentationLoader.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { ComplexLoader, Loader } from '@cloudbeaver/core-blocks'; -import type { IDatabaseDataModel, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; - -async function loader() { - const { GISValuePresentation } = await import('./GISValuePresentation'); - return { GISValuePresentation }; -} - -interface Props { - model: IDatabaseDataModel; - resultIndex: number; -} - -export const GISValuePresentationLoader: React.FC = function GISValuePresentationLoader({ model, resultIndex }) { - return ( - } - > - {({ GISValuePresentation }) => ( - - )} - - ); -} -; diff --git a/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx index 9d7209610b..515112d065 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISViewer.tsx @@ -7,13 +7,24 @@ */ import type { TabContainerPanelComponent } from '@cloudbeaver/core-blocks'; +import { ComplexLoader, Loader } from '@cloudbeaver/core-blocks'; import type { IDataValuePanelProps, IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer'; -import { GISValuePresentationLoader } from './GISValuePresentationLoader'; +async function loader() { + const { GISValuePresentation } = await import('./GISValuePresentation'); + return { GISValuePresentation }; +} export const GISViewer: TabContainerPanelComponent> = function GISViewer({ model, resultIndex }) { return ( - + } + > + {({ GISValuePresentation }) => ( + + )} + ); } ; From eb0ea0d83d3284a0e51e65bdb6dbd77b3d686866 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 30 Mar 2021 12:20:30 +0300 Subject: [PATCH 46/75] feat(plugin-gis-viewer): CB-664 add additional console.error with exception --- .../packages/plugin-gis-viewer/src/GISValuePresentation.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index c47b46fb13..bd81676338 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -55,8 +55,9 @@ export const GISValuePresentation: React.FC = observer(function GISValueP try { const parsedCellValue = wktToGeoJSON(cellValue.mapText || cellValue.text); result.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } }); - } catch { - console.error(`Failed to parse ${cellValue.mapText || cellValue.text} value`); + } catch (exception) { + console.error(`Failed to parse "${cellValue.mapText || cellValue.text}" value.`); + console.error(exception); } } From 02fb3769e926825c91cc5b709972c5349a0805ba Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 30 Mar 2021 14:55:14 +0300 Subject: [PATCH 47/75] fix(core-connections): CB-886 send provider properties default values if they exist --- .../src/FormControls/ComboboxNew.tsx | 1 + .../Options/ConnectionOptionsTabService.ts | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx index 5c5c9056cc..befc34b5ae 100644 --- a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx @@ -241,6 +241,7 @@ export const ComboboxNew: ComboboxType = observer(function ComboboxNew({ name={name} title={title} value={inputValue} + disabled={disabled} readOnly={readOnly} onChange={handleChange} {...rest} diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index d49249cd0c..c742c38059 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -186,8 +186,21 @@ export class ConnectionOptionsTabService extends Bootstrap { } } - if (Object.keys(data.config.providerProperties).length > 0) { - config.providerProperties = data.config.providerProperties; + if (driver.providerProperties.length > 0) { + const providerProperties: Record = { ...data.config.providerProperties }; + + for (const providerProperty of driver.providerProperties) { + if (providerProperty.defaultValue === null + || providerProperty.defaultValue === undefined + || !providerProperty.id + || providerProperty.id in providerProperties) { + continue; + } + + providerProperties[providerProperty.id] = providerProperty.defaultValue; + } + + config.providerProperties = providerProperties; } } From d9a720f8e7de92120b994a18b71c588f1dd7fc13 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 30 Mar 2021 19:07:33 +0300 Subject: [PATCH 48/75] fix(core-app): CB-869, CB-856 remove background color when editing react-grid cell --- .../src/NavigationTree/NavigationTreeContextMenuService.ts | 6 +++++- .../src/styles/themes/_base-react-grid.scss | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index 507314282f..d74b75845a 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -82,6 +82,10 @@ export class NavigationTreeContextMenuService extends Bootstrap { return context.contextType === NavigationTreeContextMenuService.nodeContextType && context.data.objectFeatures.includes(EObjectFeature.dataSource); }, + isHidden: context => { + const connection = this.getConnectionFromNodeId(context.data.id); + return !connection?.connected; + }, order: 2, title: 'app_navigationTree_connection_view', isPanel: true, @@ -182,5 +186,5 @@ export class NavigationTreeContextMenuService extends Bootstrap { this.registerNodeViewMenuItem(); } - load(): void {} + load(): void { } } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/styles/themes/_base-react-grid.scss b/webapp/packages/plugin-data-spreadsheet-new/src/styles/themes/_base-react-grid.scss index 8659d68489..52afbbd717 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/styles/themes/_base-react-grid.scss +++ b/webapp/packages/plugin-data-spreadsheet-new/src/styles/themes/_base-react-grid.scss @@ -13,7 +13,7 @@ $edited-color: #ff99006b; @mixin base-react-grid() { :global { .cb-react-grid-container:focus-within { - .rdg-cell-custom-selected { + .rdg-cell-custom-selected:not(.rdg-cell-custom-editing) { background-color: rgba(0, 145, 234, 0.3); } .rdg-cell-selected { From 3dedee96ecf6985f9ce7ceafebf0e808303bce98 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 30 Mar 2021 19:23:26 +0300 Subject: [PATCH 49/75] fix(core-app): CB-863 remove default onClick behavior from TreeNodeControl --- .../NavigationNode/NavigationNodeControl.tsx | 8 ++++++-- .../core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx index 8f44f9cdeb..9016180952 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { useContext } from 'react'; +import { useCallback, useContext } from 'react'; import styled, { css } from 'reshadow'; import { TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; @@ -71,8 +71,12 @@ export const NavigationNodeControl: React.FC = observer(function Navigati connected = !!connectionInfo?.connected; } + const onClickHandler = useCallback((event: React.MouseEvent) => { + context?.select(event.ctrlKey || event.metaKey); + }, [context]); + return styled(useStyles(TREE_NODE_STYLES, styles))( - + {connected && } diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx index 6a62f46f1f..2ef30759d2 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx @@ -15,11 +15,13 @@ const KEY = { }; interface Props { + onClick?: (event: React.MouseEvent) => void; className?: string; big?: boolean; } export const TreeNodeControl: React.FC = function TreeNodeControl({ + onClick, className, children, }) { @@ -41,7 +43,10 @@ export const TreeNodeControl: React.FC = function TreeNodeControl({ const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); - context?.select(event.ctrlKey || event.metaKey); + + if (onClick) { + onClick(event); + } }; return ( From e90132ad8d2ba053fa9766d2db66263b56716a01 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 30 Mar 2021 20:03:30 +0300 Subject: [PATCH 50/75] fix: server configuration --- .../AdministrationScreenService.ts | 5 +- .../Checkboxes/useCheckboxState.ts | 2 +- .../core-blocks/src/FormControls/Combobox.tsx | 4 +- .../src/FormControls/ComboboxNew.tsx | 4 +- .../src/FormControls/FormContext.ts | 12 +++- .../src/FormControls/InputField.tsx | 2 +- .../src/FormControls/InputFieldNew.tsx | 2 +- .../core-blocks/src/FormControls/Radio.tsx | 2 +- .../src/FormControls/RadioGroup.tsx | 2 +- .../src/FormControls/SubmittingForm.tsx | 26 ++++++--- .../core-blocks/src/FormControls/Textarea.tsx | 2 +- .../src/FormControls/TextareaNew.tsx | 2 +- .../core-blocks/src/Tabs/Tab/useTab.ts | 45 ++++++-------- webapp/packages/core-blocks/src/index.ts | 2 + .../packages/core-blocks/src/useExecutor.ts | 56 ++++++++++++++++++ .../core-blocks/src/useFormValidator.ts | 28 +++++++++ .../src/ConnectionForm/Options/Options.tsx | 2 +- webapp/packages/core-executor/src/index.ts | 1 - .../core-executor/src/useFormValidator.ts | 44 -------------- .../ServerConfigurationPage.tsx | 3 +- .../ServerConfigurationPageController.ts | 7 --- .../AuthenticationProviders.tsx | 58 +++++++++++++------ .../ServerConfigurationAdminForm.tsx | 5 -- ...verConfigurationAuthenticationBootstrap.ts | 57 ++++++------------ 24 files changed, 206 insertions(+), 167 deletions(-) create mode 100644 webapp/packages/core-blocks/src/useExecutor.ts create mode 100644 webapp/packages/core-blocks/src/useFormValidator.ts delete mode 100644 webapp/packages/core-executor/src/useFormValidator.ts diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index 7de9d17de4..4d42f05129 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -256,7 +256,7 @@ export class AdministrationScreenService { return true; } - private async isAccessProvided(state: RouterState) { + private async isAccessProvided(state: RouterState): Promise { await this.serverConfigResource.load(); if (this.isConfigurationMode) { @@ -264,8 +264,7 @@ export class AdministrationScreenService { } if (this.screenService.isActive(state.name, AdministrationScreenService.setupName)) { - this.navigateToRoot(); - return; + return false; } await this.ensurePermissions.execute(); diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts b/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts index da13b42b61..a2b3396b12 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/useCheckboxState.ts @@ -77,7 +77,7 @@ export function useCheckboxState(options: CheckboxStateOpti } onChange?.(checked, name as TKey); - context?.onChange(checked, name); + context?.change(checked, name); refresh(count + 1); }, diff --git a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx index 53d42610a1..182bf557e5 100644 --- a/webapp/packages/core-blocks/src/FormControls/Combobox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Combobox.tsx @@ -177,7 +177,7 @@ export const Combobox: ComboboxType = observer(function Combobox({ onSelect(null, name, value); } if (context) { - context.onChange(null, name); + context.change(null, name); } setSearchValue(''); }, @@ -195,7 +195,7 @@ export const Combobox: ComboboxType = observer(function Combobox({ onSelect(id, name, value); } if (context) { - context.onChange(id, name); + context.change(id, name); } }, [value, state, name, menu, context, onSelect] diff --git a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx index 5c5c9056cc..4eaa9663e1 100644 --- a/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/ComboboxNew.tsx @@ -195,7 +195,7 @@ export const ComboboxNew: ComboboxType = observer(function ComboboxNew({ onSelect(null, name, value); } if (context) { - context.onChange(null, name); + context.change(null, name); } setSearchValue(''); }, @@ -213,7 +213,7 @@ export const ComboboxNew: ComboboxType = observer(function ComboboxNew({ onSelect(id, name, value); } if (context) { - context.onChange(id, name); + context.change(id, name); } setSearchValue(''); }, diff --git a/webapp/packages/core-blocks/src/FormControls/FormContext.ts b/webapp/packages/core-blocks/src/FormControls/FormContext.ts index e8c459a9ee..bb99953047 100644 --- a/webapp/packages/core-blocks/src/FormControls/FormContext.ts +++ b/webapp/packages/core-blocks/src/FormControls/FormContext.ts @@ -8,8 +8,18 @@ import { createContext } from 'react'; +import type { IExecutor } from '@cloudbeaver/core-executor'; + +type ChangeHandler = (value: string | number | boolean | null | undefined, name: string | undefined) => void; + +export interface IChangeData { + value: string | number | boolean | null | undefined; + name: string | undefined; +} + export interface IFormContext { - onChange: (value: string | number | boolean | null | undefined, name: string | undefined) => void; + changeExecutor: IExecutor; + change: ChangeHandler; } export const FormContext = createContext(null); diff --git a/webapp/packages/core-blocks/src/FormControls/InputField.tsx b/webapp/packages/core-blocks/src/FormControls/InputField.tsx index 2504899ab0..4f4eb8e869 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputField.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputField.tsx @@ -72,7 +72,7 @@ export const InputField: InputFieldType = observer(function InputField({ onChange(event.target.value, name); } if (context) { - context.onChange(event.target.value, name); + context.change(event.target.value, name); } }, [state, name, context, onChange]); diff --git a/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx b/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx index 4a25cec7a9..bdbc6d80a1 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx @@ -92,7 +92,7 @@ export const InputFieldNew: InputFieldType = observer(function InputFieldNew({ onChange(value, name); } if (context) { - context.onChange(value, name); + context.change(value, name); } }, [state, name, context, onChange]); diff --git a/webapp/packages/core-blocks/src/FormControls/Radio.tsx b/webapp/packages/core-blocks/src/FormControls/Radio.tsx index 5778d2db31..e4243695f6 100644 --- a/webapp/packages/core-blocks/src/FormControls/Radio.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Radio.tsx @@ -144,7 +144,7 @@ export const Radio: RadioType = observer(function Radio({ if (context) { context.onChange(value); } else if (formContext) { - formContext.onChange(value, name); + formContext.change(value, name); } if (onChange) { diff --git a/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx b/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx index da3f64c621..7db736b4a1 100644 --- a/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx +++ b/webapp/packages/core-blocks/src/FormControls/RadioGroup.tsx @@ -60,7 +60,7 @@ export const RadioGroup: RadioGroupType = observer(function RadioGroup({ } if (formContext) { - formContext.onChange(value, name); + formContext.change(value, name); } }, [name, state, formContext, onChange]); diff --git a/webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx b/webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx index cd29009f32..fdddb31999 100644 --- a/webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx +++ b/webapp/packages/core-blocks/src/FormControls/SubmittingForm.tsx @@ -6,9 +6,12 @@ * you may not use this file except in compliance with the License. */ -import { useCallback, forwardRef, useMemo } from 'react'; +import { useCallback, forwardRef, useState, useContext } from 'react'; -import { FormContext } from './FormContext'; +import { Executor } from '@cloudbeaver/core-executor'; + +import { useObjectRef } from '../useObjectRef'; +import { FormContext, IChangeData, IFormContext } from './FormContext'; type FormDetailedProps = Omit, HTMLFormElement>, 'onChange'> & { disabled?: boolean; @@ -25,14 +28,23 @@ export const SubmittingForm = forwardRef(fun }, ref ) { + const parentContext = useContext(FormContext); + const props = useObjectRef({ parentContext, onChange, onSubmit }); + const [changeExecutor] = useState(() => new Executor()); + const handleSubmit = useCallback((e: React.FormEvent) => { e.preventDefault(); - if (onSubmit) { - onSubmit(e); - } - }, [onSubmit]); + props.onSubmit?.(e); + }, []); - const context = useMemo(() => ({ onChange }), [onChange]); + const context = useObjectRef({ + changeExecutor, + change(value, name) { + props.onChange(value, name); + props.parentContext?.change(value, name); + changeExecutor.execute({ value, name }); + }, + }, {}); return (
diff --git a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx index 5327edf664..31f2315fb6 100644 --- a/webapp/packages/core-blocks/src/FormControls/Textarea.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Textarea.tsx @@ -68,7 +68,7 @@ export const Textarea: TextareaType = observer(function Textarea({ onChange(event.target.value, name); } if (context) { - context.onChange(event.target.value, name); + context.change(event.target.value, name); } }, [state, name, onChange]); diff --git a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx index bc532f9446..27273f9338 100644 --- a/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/TextareaNew.tsx @@ -93,7 +93,7 @@ export const TextareaNew: TextareaType = observer(function TextareaNew({ onChange(event.target.value, name); } if (context) { - context.onChange(event.target.value, name); + context.change(event.target.value, name); } }, [state, name, onChange]); diff --git a/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts b/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts index 43a059b9c4..b0ca5ccf02 100644 --- a/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts +++ b/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts @@ -6,11 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { useContext, useEffect } from 'react'; +import { useContext } from 'react'; -import type { IExecutorHandler } from '@cloudbeaver/core-executor'; - -import { useObjectRef } from '../../useObjectRef'; +import { useExecutor } from '../../useExecutor'; import type { ITabData } from '../TabsContainer/ITabsContainer'; import { TabsContext } from '../TabsContext'; @@ -24,34 +22,25 @@ export function useTab( throw new Error('TabsContext not provided'); } - const dynamic = useObjectRef({ - tabId, - open: onOpen, - close: onClose, + useExecutor({ + executor: state.openExecutor, + handlers: [function openHandler(data) { + if (tabId !== data.tabId) { + return; + } + onOpen?.(data); + }], }); - useEffect(() => { - const openHandler: IExecutorHandler> = data => { - if (dynamic.tabId !== data.tabId) { + useExecutor({ + executor: state.closeExecutor, + handlers: [function closeHandler(data) { + if (tabId !== data.tabId) { return; } - dynamic.open?.(data); - }; - const closeHandler: IExecutorHandler> = data => { - if (dynamic.tabId !== data.tabId) { - return; - } - dynamic.close?.(data); - }; - - state.openExecutor.addHandler(openHandler); - state.closeExecutor.addHandler(closeHandler); - - return () => { - state.openExecutor.removeHandler(openHandler); - state.closeExecutor.removeHandler(closeHandler); - }; - }, [state.openExecutor, state.closeExecutor]); + onClose?.(data); + }], + }); const handleOpen = () => state.open(tabId); diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index f54bafabea..5098008ea8 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -71,7 +71,9 @@ export * from './StaticImage'; export * from './TextPlaceholder'; export * from './useClipboard'; export * from './useDataResource'; +export * from './useExecutor'; export * from './useFocus'; +export * from './useFormValidator'; export * from './useMapResource'; export * from './useObjectRef'; export * from './useStateDelay'; diff --git a/webapp/packages/core-blocks/src/useExecutor.ts b/webapp/packages/core-blocks/src/useExecutor.ts new file mode 100644 index 0000000000..af015f609a --- /dev/null +++ b/webapp/packages/core-blocks/src/useExecutor.ts @@ -0,0 +1,56 @@ +/* + * 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 { useEffect } from 'react'; + +import type { IExecutor, IExecutorHandler, IExecutorHandlersCollection } from '@cloudbeaver/core-executor'; + +import { useObjectRef } from './useObjectRef'; + +interface IUseExecutorOptions { + executor: IExecutor | IExecutorHandlersCollection; + handlers?: Array>; + postHandlers?: Array>; +} + +export function useExecutor(options: IUseExecutorOptions): void { + const props = useObjectRef(options); + + useEffect(() => { + if (!props.executor) { + return; + } + + const handlers: Array> = []; + const postHandlers: Array> = []; + + if (props.handlers) { + for (let i = 0; i < props.handlers.length; i++) { + const handler: IExecutorHandler = (data, contexts) => props.handlers?.[i](data, contexts); + props.executor.addHandler(handler); + handlers.push(handler); + } + } + if (props.postHandlers) { + for (let i = 0; i < props.postHandlers.length; i++) { + const handler: IExecutorHandler = (data, contexts) => props.postHandlers?.[i](data, contexts); + props.executor.addPostHandler(handler); + postHandlers.push(handler); + } + } + + return () => { + for (const handler of handlers) { + props.executor.removeHandler(handler); + } + for (const handler of postHandlers) { + props.executor.removePostHandler(handler); + } + }; + }, [props.executor, props.handlers?.length, props.postHandlers?.length]); +} diff --git a/webapp/packages/core-blocks/src/useFormValidator.ts b/webapp/packages/core-blocks/src/useFormValidator.ts new file mode 100644 index 0000000000..46952aa43d --- /dev/null +++ b/webapp/packages/core-blocks/src/useFormValidator.ts @@ -0,0 +1,28 @@ + +/* + * 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 type { IExecutor, IExecutorHandlersCollection } from '@cloudbeaver/core-executor'; + +import { useExecutor } from './useExecutor'; + +export function useFormValidator( + validationTask: IExecutor | IExecutorHandlersCollection, + ref: React.RefObject, + callback?: () => void +): void { + useExecutor({ + executor: validationTask, + handlers: [function validate() { + ref.current?.checkValidity(); + ref.current?.reportValidity(); + + callback?.(); + }], + }); +} diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx b/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx index 3128284e90..b0dc766be4 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx @@ -26,9 +26,9 @@ import { TextareaNew, ComboboxNew, Container, + useFormValidator, } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; -import { useFormValidator } from '@cloudbeaver/core-executor'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; diff --git a/webapp/packages/core-executor/src/index.ts b/webapp/packages/core-executor/src/index.ts index f246f6ca14..230527d799 100644 --- a/webapp/packages/core-executor/src/index.ts +++ b/webapp/packages/core-executor/src/index.ts @@ -8,4 +8,3 @@ export * from './IExecutionContext'; export * from './IExecutor'; export * from './IExecutorHandler'; export * from './IExecutorHandlersCollection'; -export * from './useFormValidator'; diff --git a/webapp/packages/core-executor/src/useFormValidator.ts b/webapp/packages/core-executor/src/useFormValidator.ts deleted file mode 100644 index 5a12697db0..0000000000 --- a/webapp/packages/core-executor/src/useFormValidator.ts +++ /dev/null @@ -1,44 +0,0 @@ - -/* - * 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 { useEffect, useRef } from 'react'; - -import type { IExecutor } from './IExecutor'; -import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection'; - -export function useFormValidator( - validationTask: IExecutor | IExecutorHandlersCollection, - ref: React.RefObject, - callback?: () => void -): void { - const callbackRef = useRef({ - callback, - ref, - }); - callbackRef.current.callback = callback; - callbackRef.current.ref = ref; - - useEffect(() => { - if (!validationTask) { - return; - } - - function validate() { - if (callbackRef.current) { - callbackRef.current.ref.current?.checkValidity(); - callbackRef.current.ref.current?.reportValidity(); - - callbackRef.current.callback?.(); - } - } - validationTask.addHandler(validate); - - return () => validationTask.removeHandler(validate); - }, [validationTask]); -} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index ed17437595..37502ef053 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -10,9 +10,8 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; import { AdministrationItemContentComponent, AdministrationTools, ADMINISTRATION_TOOLS_STYLES } from '@cloudbeaver/core-administration'; -import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus, useFormValidator } from '@cloudbeaver/core-blocks'; import { useController, useService } from '@cloudbeaver/core-di'; -import { useFormValidator } from '@cloudbeaver/core-executor'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPageController.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPageController.ts index ecb94ca893..f491f1e154 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPageController.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPageController.ts @@ -37,13 +37,6 @@ export class ServerConfigurationPageController { change = (): void => { this.serverConfigurationService.changed(); - if (this.state.serverConfig.enabledAuthProviders?.length === 0) { - this.state.serverConfig.authenticationEnabled = false; - } - - if (!this.state.serverConfig.authenticationEnabled) { - this.state.serverConfig.anonymousAccessEnabled = true; - } if (!this.state.serverConfig.adminCredentialsSaveEnabled) { this.state.serverConfig.publicCredentialsSaveEnabled = false; diff --git a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx index f2c971d052..11a8d790f0 100644 --- a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/AuthenticationProviders.tsx @@ -7,10 +7,11 @@ */ import { observer } from 'mobx-react-lite'; +import { useContext } from 'react'; import styled from 'reshadow'; import { AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; -import { BASE_CONTAINERS_STYLES, Container, Group, GroupTitle, Loader, PlaceholderComponent, SwitchNew, useMapResource } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, Container, FormContext, Group, GroupTitle, Loader, PlaceholderComponent, SwitchNew, useExecutor, useMapResource } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration'; @@ -24,8 +25,33 @@ export const AuthenticationProviders: PlaceholderComponent 1; + + useExecutor({ + executor: formContext.changeExecutor, + handlers: [function switchControls() { + if (externalAuthentication) { + serverConfig.enabledAuthProviders = [...providers.resource.keys]; + serverConfig.authenticationEnabled = true; + } + + if (serverConfig.enabledAuthProviders?.length === 0) { + serverConfig.authenticationEnabled = false; + } + + if (!serverConfig.authenticationEnabled) { + serverConfig.anonymousAccessEnabled = true; + } + }], + }); return styled(styles)( @@ -42,21 +68,19 @@ export const AuthenticationProviders: PlaceholderComponent {translate('administration_configuration_wizard_configuration_anonymous_access')} - {!disabled && ( - - {translate('administration_configuration_wizard_configuration_authentication')} - - )} + + {translate('administration_configuration_wizard_configuration_authentication')} + - {() => !disabled && styled(styles)( + {() => providersSelectable && styled(styles)( <> {translate('administration_configuration_wizard_configuration_authentication_provider')} {providers.data.map(provider => provider && ( @@ -77,7 +101,7 @@ export const AuthenticationProviders: PlaceholderComponent - {configurationWizard ? ( + {configurationWizard && localExists ? ( ) : ( diff --git a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx index d594cb6460..8f6694920e 100644 --- a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAdminForm.tsx @@ -10,7 +10,6 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { BASE_CONTAINERS_STYLES, Group, GroupTitle, InputFieldNew } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { ServerConfigInput } from '@cloudbeaver/core-sdk'; @@ -26,10 +25,6 @@ export const ServerConfigurationAdminForm: React.FC = observer(function S const translate = useTranslate(); const style = useStyles(BASE_CONTAINERS_STYLES); - if (!serverConfig.enabledAuthProviders?.includes(AUTH_PROVIDER_LOCAL_ID)) { - return null; - } - return styled(style)( {translate('administration_configuration_wizard_configuration_admin')} diff --git a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts index f5c78ebc0f..dc41afccfe 100644 --- a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts @@ -26,26 +26,18 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { register(): void { this.serverConfigurationService.validationTask.addHandler(this.validateForm); - this.serverConfigurationService.prepareConfigTask.addHandler(this.prepareConfig); this.serverConfigurationService.loadConfigTask.addHandler(this.loadServerConfig); } load(): void { } private loadServerConfig: IExecutorHandler = async (data, contexts) => { - const providers = await this.authProvidersResource.loadAll(); - const disabled = providers.length === 1 && !this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); - - if (disabled) { - data.state.serverConfig.enabledAuthProviders = [...this.authProvidersResource.keys]; - data.state.serverConfig.authenticationEnabled = true; - } - if (!data.reload) { return; } try { + await this.authProvidersResource.loadAll(); const config = await this.serverConfigResource.load(); if (!config) { @@ -53,16 +45,17 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { } if (config.configurationMode) { - data.state.serverConfig.adminName = 'cbadmin'; - data.state.serverConfig.adminPassword = ''; - - data.state.serverConfig.anonymousAccessEnabled = false; - data.state.serverConfig.authenticationEnabled = true; + if (this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID)) { + data.state.serverConfig.adminName = 'cbadmin'; + data.state.serverConfig.adminPassword = ''; + } } else { - data.state.serverConfig.anonymousAccessEnabled = config.anonymousAccessEnabled; - data.state.serverConfig.authenticationEnabled = config.authenticationEnabled; + data.state.serverConfig.adminName = undefined; + data.state.serverConfig.adminPassword = undefined; } + data.state.serverConfig.anonymousAccessEnabled = config.anonymousAccessEnabled; + data.state.serverConfig.authenticationEnabled = config.authenticationEnabled; data.state.serverConfig.enabledAuthProviders = config.enabledAuthProviders; } catch (exception) { ExecutorInterrupter.interrupt(contexts); @@ -70,37 +63,21 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { } }; - private prepareConfig: IExecutorHandler = async (data, contexts) => { - const providers = await this.authProvidersResource.loadAll(); - const disabled = providers.length === 1 && !this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); + private validateForm: IExecutorHandler = async (data, contexts) => { + await this.authProvidersResource.loadAll(); + const administratorPresented = data.configurationWizard && this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID); - if (disabled) { - data.state.serverConfig.enabledAuthProviders = [...this.authProvidersResource.keys]; - data.state.serverConfig.authenticationEnabled = true; + if (!administratorPresented) { + return; } - if ( - !data.configurationWizard - || !data.state.serverConfig.enabledAuthProviders?.includes(AUTH_PROVIDER_LOCAL_ID) - ) { - data.state.serverConfig.adminName = undefined; - data.state.serverConfig.adminPassword = undefined; - } - }; - - private validateForm: IExecutorHandler = (data, contexts) => { const validation = contexts.getContext(serverConfigValidationContext); - if ( - data.configurationWizard - && data.state.serverConfig.enabledAuthProviders?.includes(AUTH_PROVIDER_LOCAL_ID) - ) { - if (!data.state.serverConfig.adminName + if (!data.state.serverConfig.adminName || data.state.serverConfig.adminName.length < 6 || !data.state.serverConfig.adminPassword - ) { - validation.invalidate(); - } + ) { + validation.invalidate(); } }; } From 18b295cb403adc8aa9a7a15ae463bd0cfaf4c109 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 31 Mar 2021 14:12:34 +0300 Subject: [PATCH 51/75] fix: CB-727 CB-881 authentication progress indication --- webapp/packages/core-blocks/src/Button.tsx | 3 ++- .../packages/plugin-authentication/src/Dialog/AuthDialog.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-blocks/src/Button.tsx b/webapp/packages/core-blocks/src/Button.tsx index 72930be907..489188c3dc 100644 --- a/webapp/packages/core-blocks/src/Button.tsx +++ b/webapp/packages/core-blocks/src/Button.tsx @@ -102,6 +102,7 @@ export const Button: React.FC = function Button({ tag = 'button', disabled = false, loading, + className, ...rest }) { if (loading) { @@ -110,7 +111,7 @@ export const Button: React.FC = function Button({ const Button = tag; return styled(useStyles(buttonStyles, ...(mod || []).map(mod => buttonMod[mod])))( - - - ); - } + return styled(styles)( + + + + ); +} ); From db03e7c4da338032ee5df328dd7bc68d7eabcfa4 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 31 Mar 2021 14:58:59 +0300 Subject: [PATCH 54/75] chore: cleanup code --- .../ConfigurationWizardPagesBootstrapService.ts | 2 +- .../ServerConfiguration/ServerConfigurationService.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts index d4e6d665b5..4c83787b98 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts @@ -56,7 +56,7 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap { ), }, order: 4, - onActivate: this.serverConfigurationService.activate.bind(this.serverConfigurationService), + onActivate: this.serverConfigurationService.loadConfig.bind(this.serverConfigurationService), getContentComponent: () => ServerConfigurationPage, getDrawerComponent: () => ServerConfigurationDrawerItem, }); diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 305986484b..403643e9b1 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -117,10 +117,6 @@ export class ServerConfigurationService { return this.done; } - async activate(): Promise { - await this.loadConfig(); - } - async saveConfiguration(finish: boolean): Promise { const contexts = await this.saveTask.execute(this.getSaveData(finish)); From 9363dae717e3dd42b7c582f517e5aa123e68e920 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 31 Mar 2021 15:22:24 +0300 Subject: [PATCH 55/75] fix: CB-792 CB-730 CB-906 authentication & administration --- .../AdministrationScreenService.ts | 2 +- .../NavigationTreeContextMenuService.ts | 3 +- .../NavigationTabs/NavigationTabsService.ts | 4 ++ .../src/AuthInfoService.ts | 4 +- .../src/UserInfoResource.ts | 12 +++- .../src/ConnectionInfoResource.ts | 3 +- webapp/packages/core-connections/src/index.ts | 1 - webapp/packages/core-di/src/App.ts | 3 + webapp/packages/core-di/src/Dependency.ts | 13 ++++ webapp/packages/core-di/src/index.ts | 1 + .../src/CommonDialog/CommonDialogService.ts | 2 +- .../core-executor/src/IExecutionContext.ts | 2 +- .../src/TaskScheduler/TaskScheduler.ts | 33 ++++++---- .../src/ConnectionNavigatorViewSettings.ts | 0 .../core-root/src/ServerConfigResource.ts | 62 +++++++++++++++++-- .../packages/core-root/src/SessionResource.ts | 4 +- webapp/packages/core-root/src/index.ts | 1 + .../src/Resource/CachedDataResource.ts | 1 + .../core-sdk/src/Resource/CachedResource.ts | 43 +++++++------ .../src/queries/authentication/authLogin.gql | 3 +- .../queries/authentication/getActiveUser.gql | 1 + webapp/packages/core-sdk/src/sdk.ts | 14 ++++- .../ServerConfigurationNavigatorViewForm.tsx | 2 +- .../ServerConfigurationPage.tsx | 8 ++- .../ServerConfigurationService.ts | 2 +- .../src/AuthenticationService.ts | 13 ++-- .../src/Dialog/AuthDialog.tsx | 17 +++-- .../src/Dialog/AuthDialogController.ts | 6 +- .../src/Dialog/AuthDialogService.ts | 4 +- 29 files changed, 187 insertions(+), 77 deletions(-) create mode 100644 webapp/packages/core-di/src/Dependency.ts rename webapp/packages/{core-connections => core-root}/src/ConnectionNavigatorViewSettings.ts (100%) diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index 4d42f05129..20eae196eb 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -86,7 +86,7 @@ export class AdministrationScreenService { this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE); this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO); - this.permissionsResource.onDataUpdate.addHandler(() => { + this.permissionsResource.onDataUpdate.addPostHandler(() => { this.checkPermissions(this.screenService.routerService.state); }); } diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index d74b75845a..c76e3a614c 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -6,10 +6,11 @@ * you may not use this file except in compliance with the License. */ -import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-connections'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; +import { isNavigatorViewSettingsEqual, CONNECTION_NAVIGATOR_VIEW_SETTINGS, NavigatorViewSettings } from '@cloudbeaver/core-root'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; diff --git a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts index 4fb1e76649..211eb0a9b8 100644 --- a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts +++ b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts @@ -315,6 +315,10 @@ export class NavigationTabsService { } private async restoreTab(tab: ITab, removedTabs: string[]): Promise { + if (tab.restored) { + return; + } + let restoreFail = false; try { const handler = this.handlers.get(tab.handlerId); diff --git a/webapp/packages/core-authentication/src/AuthInfoService.ts b/webapp/packages/core-authentication/src/AuthInfoService.ts index fbc3bd4d20..f8d0769674 100644 --- a/webapp/packages/core-authentication/src/AuthInfoService.ts +++ b/webapp/packages/core-authentication/src/AuthInfoService.ts @@ -21,8 +21,8 @@ export class AuthInfoService { return this.userInfoResource.data; } - async login(provider: string, credentials: Record): Promise { - return this.userInfoResource.login(provider, credentials); + async login(provider: string, credentials: Record, link?: boolean): Promise { + return this.userInfoResource.login(provider, credentials, link); } async logout(): Promise { diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 331a3fd846..34bba9c15b 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -23,9 +23,14 @@ export class UserInfoResource extends CachedDataResource super(null); this.sync(this.sessionResource); + this.sessionDataResource.beforeLoad.addHandler(() => this.load()); this.onDataOutdated.addHandler(() => this.sessionDataResource.markOutdated()); } + isLinked(provideId: string): boolean { + return this.data?.linkedAuthProviders.includes(provideId) || false; + } + getId(): string { return this.data?.userId || 'anonymous'; } @@ -40,7 +45,7 @@ export class UserInfoResource extends CachedDataResource || this.data.authTokens.some(token => token.origin.type === type && token.origin.subType === subType); } - async login(provider: string, credentials: Record): Promise { + async login(provider: string, credentials: Record, link?: boolean): Promise { await this.performUpdate(undefined, undefined, async () => { const processedCredentials = await this.authProviderService.processCredentials(provider, credentials); @@ -48,10 +53,11 @@ export class UserInfoResource extends CachedDataResource const { authToken } = await this.graphQLService.sdk.authLogin({ provider, credentials: processedCredentials, + linkUser: link, customIncludeOriginDetails: true, }); - if (this.data === null) { + if (this.data === null || link) { this.data = await this.loader(); } else { this.data.authTokens.push(authToken as UserAuthToken); @@ -67,9 +73,9 @@ export class UserInfoResource extends CachedDataResource if (this.data) { await this.graphQLService.sdk.authLogout(); this.data = null; - this.sessionDataResource.refresh(); } }); + await this.sessionDataResource.refresh(); } protected async loader(): Promise { diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 2084a4de93..f8f2b9b360 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -10,7 +10,7 @@ import { action, makeObservable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; -import { SessionDataResource } from '@cloudbeaver/core-root'; +import { NavigatorViewSettings, SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -26,7 +26,6 @@ import { } from '@cloudbeaver/core-sdk'; import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource'; -import type { NavigatorViewSettings } from './ConnectionNavigatorViewSettings'; export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] }; export type ConnectionInitConfig = Omit; diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 59b51d44b2..22a5209671 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -32,4 +32,3 @@ export * from './NetworkHandlerResource'; export * from './useConnectionInfo'; export * from './useDBDriver'; export * from './getUniqueConnectionName'; -export * from './ConnectionNavigatorViewSettings'; diff --git a/webapp/packages/core-di/src/App.ts b/webapp/packages/core-di/src/App.ts index 51bb6b627d..250ed1145e 100644 --- a/webapp/packages/core-di/src/App.ts +++ b/webapp/packages/core-di/src/App.ts @@ -7,6 +7,7 @@ */ import { Bootstrap } from './Bootstrap'; +import { Dependency } from './Dependency'; import type { DIContainer } from './DIContainer'; import { RootContainerService } from './entities/RootContainerService'; import type { IServiceCollection, IServiceInjector } from './IApp'; @@ -62,6 +63,8 @@ export class App { if ('register' in serviceInstance) { await serviceInstance.register(); } + } else if (service.prototype instanceof Dependency) { + this.diWrapper.injector.getServiceByClass(service); } } } diff --git a/webapp/packages/core-di/src/Dependency.ts b/webapp/packages/core-di/src/Dependency.ts new file mode 100644 index 0000000000..e95f7fc2ff --- /dev/null +++ b/webapp/packages/core-di/src/Dependency.ts @@ -0,0 +1,13 @@ +/* + * 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 { injectable } from './injectable'; + +@injectable() +export abstract class Dependency { +} diff --git a/webapp/packages/core-di/src/index.ts b/webapp/packages/core-di/src/index.ts index af0b07fbd7..94c7ec9e62 100644 --- a/webapp/packages/core-di/src/index.ts +++ b/webapp/packages/core-di/src/index.ts @@ -2,6 +2,7 @@ export * from './IApp'; export * from './App'; export * from './AppContext'; export * from './Bootstrap'; +export * from './Dependency'; export * from './injectable'; export * from './PluginManifest'; export * from './useService'; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts index 22732aec9c..c9f8528602 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts @@ -40,7 +40,7 @@ export interface DialogInternal { @injectable() export class CommonDialogService { - dialogs: DialogInternal[] = observable([]); + dialogs: DialogInternal[] = observable([], { deep: false }); // note that if dialog is closed by user it will be resolved with DialogueStateResult.Rejected async open( diff --git a/webapp/packages/core-executor/src/IExecutionContext.ts b/webapp/packages/core-executor/src/IExecutionContext.ts index 97963cdc0e..06e6dc06d9 100644 --- a/webapp/packages/core-executor/src/IExecutionContext.ts +++ b/webapp/packages/core-executor/src/IExecutionContext.ts @@ -12,7 +12,7 @@ export interface IContextGetter { } export interface IExecutionContextProvider { - hasContext: (token: IContextLoader) => boolean; + hasContext: (token: IContextLoader) => boolean; getContext: IContextGetter; } diff --git a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts index 4d5cfc465c..5db20bb715 100644 --- a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts +++ b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts @@ -17,6 +17,10 @@ export class TaskScheduler { return this.queue.map(task => task.id); } + get executing(): boolean { + return this.queue.length > 0; + } + private readonly queue: Array>; private readonly isBlocked: BlockedExecution | null; @@ -53,7 +57,6 @@ export class TaskScheduler { await error?.(exception); throw exception; } finally { - this.queue.splice(this.queue.indexOf(task), 1); await after?.(); } } @@ -72,18 +75,22 @@ export class TaskScheduler { id: TIdentifier, promise: () => Promise, ) { - if (!this.isBlocked) { - return promise(); + try { + if (!this.isBlocked) { + return await promise(); + } + + const queueList = this.queue.filter(active => this.isBlocked!(active.id, id)); + + for (const task of queueList) { + try { + await task.task; + } catch {} + } + + return await promise(); + } finally { + this.queue.splice(this.queue.findIndex(task => task.id === id), 1); } - - const queueList = this.queue.filter(active => this.isBlocked!(active.id, id)); - - for (const task of queueList) { - try { - await task.task; - } catch {} - } - - return promise(); } } diff --git a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts b/webapp/packages/core-root/src/ConnectionNavigatorViewSettings.ts similarity index 100% rename from webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts rename to webapp/packages/core-root/src/ConnectionNavigatorViewSettings.ts diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 92fc289f26..ea133fb7b7 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -9,7 +9,9 @@ import { makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; -import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput } from '@cloudbeaver/core-sdk'; +import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput, NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; + +import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings'; @injectable() export class ServerConfigResource extends CachedDataResource { @@ -59,23 +61,73 @@ export class ServerConfigResource extends CachedDataResource { + async setDefaultNavigatorSettings(settings: NavigatorSettingsInput): Promise { + await this.performUpdate(undefined, undefined, async () => { + await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings }); + + if (this.data) { + this.data.defaultNavigatorSettings = { ...settings }; + } else { + this.data = await this.loader(); + } + }, () => !this.isNavigatorSettingsChanged(settings)); + } + + async save(onlyRestart = false): Promise { await this.performUpdate(undefined, undefined, async () => { await this.graphQLService.sdk.configureServer({ - configuration: this.update, + configuration: onlyRestart ? {} : this.update, }); this.data = await this.loader(); - }); + }, () => !this.isChanged() && !onlyRestart); } protected async loader(): Promise { const { serverConfig } = await this.graphQLService.sdk.serverConfig(); + this.syncUpdateData(serverConfig); + + return serverConfig as ServerConfig; + } + + private syncUpdateData(serverConfig: ServerConfig) { this.update.serverName = serverConfig.name; this.update.sessionExpireTime = serverConfig.sessionExpireTime; @@ -90,7 +142,5 @@ export class ServerConfigResource extends CachedDataResource { constructor( private graphQLService: GraphQLService, - private serverConfiguration: ServerConfigResource + serverConfiguration: ServerConfigResource ) { super(null); - this.sync(this.serverConfiguration); + this.sync(serverConfiguration); } protected async loader(): Promise { diff --git a/webapp/packages/core-root/src/index.ts b/webapp/packages/core-root/src/index.ts index 47de13727c..54c1c80c4f 100644 --- a/webapp/packages/core-root/src/index.ts +++ b/webapp/packages/core-root/src/index.ts @@ -1,3 +1,4 @@ +export * from './ConnectionNavigatorViewSettings'; export * from './PermissionsResource'; export * from './PermissionsService'; export * from './ServerConfigResource'; diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts index b0b2da2496..4b24063928 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts @@ -35,6 +35,7 @@ export abstract class CachedDataResource< markUpdated(param: TParam): void { const metadata = this.metadata.get(param as unknown as TKey); metadata.outdated = false; + metadata.exception = null; this.loaded = true; } diff --git a/webapp/packages/core-sdk/src/Resource/CachedResource.ts b/webapp/packages/core-sdk/src/Resource/CachedResource.ts index 0e0a976488..9c5ff0e9a8 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResource.ts @@ -6,8 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable } from 'mobx'; +import { observable, makeObservable, action } from 'mobx'; +import { Dependency } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor, TaskScheduler } from '@cloudbeaver/core-executor'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -44,7 +45,7 @@ export abstract class CachedResource< TParam, TKey = TParam, TContext = void -> { +> extends Dependency { data: TData; readonly onDataOutdated: IExecutor; @@ -54,15 +55,23 @@ export abstract class CachedResource< protected metadata: MetadataMap; - protected loading = false; + protected get loading(): boolean { + return this.scheduler.executing; + } protected scheduler: TaskScheduler; protected paramAliases: Array>; constructor(defaultValue: TData) { - makeObservable, 'loading'>(this, { + super(); + makeObservable, 'loader'>(this, { data: observable, - loading: observable, + loader: action, + markDataLoading: action, + markDataLoaded: action, + markDataError: action, + markOutdated: action, + markUpdated: action, }); this.includes = this.includes.bind(this); @@ -82,7 +91,7 @@ export abstract class CachedResource< resource: CachedResource, context: TContext ): void { - resource.onDataOutdated.addHandler(param => this.markOutdated(param)); + resource.onDataOutdated.addHandler(this.markOutdated.bind(this)); resource.onDataUpdate.addHandler(param => this.load(param, context)); this.beforeLoad.addHandler(param => resource.load(param)); } @@ -207,22 +216,24 @@ export abstract class CachedResource< } param = this.transformParam(param); + const contexts = await this.beforeLoad.execute(param); + + if (ExecutorInterrupter.isInterrupted(contexts)) { + return; + } + this.markDataLoading(param, context); - this.loading = true; return this.scheduler.schedule( param, async () => { - // repeated because previous task maybe has been load requested data + // repeated because previous task maybe has been load requested data if (exitCheck?.()) { return; } return await this.taskWrapper(param, context, update); }, - () => { - this.markDataLoaded(param, context); - this.loading = false; - }, + () => this.markDataLoaded(param, context), () => this.onDataUpdate.execute(param), exception => this.markDataError(exception, param, context)); } @@ -240,21 +251,17 @@ export abstract class CachedResource< } this.markDataLoading(param, context); - this.loading = true; await this.scheduler.schedule( param, async () => { - // repeated because previous task maybe has been load requested data + // repeated because previous task maybe has been load requested data if (this.isLoaded(param, context) && !this.isOutdated(param) && !refresh) { return; } await this.taskWrapper(param, context, this.loadingTask); }, - () => { - this.markDataLoaded(param, context); - this.loading = false; - }, + () => this.markDataLoaded(param, context), () => this.onDataUpdate.execute(param), exception => this.markDataError(exception, param, context)); } diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql index d4c30a474a..601c32a823 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql @@ -1,9 +1,10 @@ query authLogin( $provider: ID! $credentials: Object! + $linkUser: Boolean $customIncludeOriginDetails: Boolean! ) { - authToken: authLogin(provider: $provider, credentials: $credentials) { + authToken: authLogin(provider: $provider, credentials: $credentials, linkUser: $linkUser) { ...AuthToken } } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql b/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql index 9a7c1e3167..134e6717af 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql @@ -2,6 +2,7 @@ query getActiveUser($customIncludeOriginDetails: Boolean!) { user: activeUser { userId displayName + linkedAuthProviders authTokens { ...AuthToken } diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 4cfa4ffe0d..1dde80cd51 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -77,6 +77,7 @@ export interface QueryAllConnectionsArgs { export interface QueryAuthLoginArgs { provider: Scalars['ID']; credentials: Scalars['Object']; + linkUser?: Maybe; } export interface QueryAuthLogoutArgs { @@ -935,6 +936,7 @@ export interface UserInfo { userId: Scalars['String']; displayName?: Maybe; authTokens: UserAuthToken[]; + linkedAuthProviders: Array; } export interface DataTransferProcessorInfo { @@ -967,6 +969,7 @@ export interface AsyncTaskCancelMutation { result: Mutation['asyncTaskCancel'] } export type AuthLoginQueryVariables = Exact<{ provider: Scalars['ID']; credentials: Scalars['Object']; + linkUser?: Maybe; customIncludeOriginDetails: Scalars['Boolean']; }>; @@ -982,7 +985,7 @@ export type GetActiveUserQueryVariables = Exact<{ export interface GetActiveUserQuery { user?: Maybe<( - Pick + Pick & { authTokens: AuthTokenFragment[] } )>; } @@ -1819,8 +1822,12 @@ export const AsyncTaskCancelDocument = ` } `; export const AuthLoginDocument = ` - query authLogin($provider: ID!, $credentials: Object!, $customIncludeOriginDetails: Boolean!) { - authToken: authLogin(provider: $provider, credentials: $credentials) { + query authLogin($provider: ID!, $credentials: Object!, $linkUser: Boolean, $customIncludeOriginDetails: Boolean!) { + authToken: authLogin( + provider: $provider + credentials: $credentials + linkUser: $linkUser + ) { ...AuthToken } } @@ -1835,6 +1842,7 @@ export const GetActiveUserDocument = ` user: activeUser { userId displayName + linkedAuthProviders authTokens { ...AuthToken } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx index 40d2177870..f24e95fca7 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx @@ -11,8 +11,8 @@ import React, { useCallback } from 'react'; import styled from 'reshadow'; import { BASE_CONTAINERS_STYLES, SwitchNew } from '@cloudbeaver/core-blocks'; -import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-connections'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-root'; import { useStyles } from '@cloudbeaver/core-theming'; import type { IServerConfigurationPageState } from '../IServerConfigurationPageState'; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index 37502ef053..9a966da4df 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -13,6 +13,7 @@ import { AdministrationItemContentComponent, AdministrationTools, ADMINISTRATION import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus, useFormValidator } from '@cloudbeaver/core-blocks'; import { useController, useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; import { useStyles } from '@cloudbeaver/core-theming'; import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm'; @@ -42,16 +43,19 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser const translate = useTranslate(); const style = useStyles(styles, ADMINISTRATION_TOOLS_STYLES, BASE_CONTAINERS_STYLES); const [focusedRef] = useFocus({ focusFirstChild: true }); + const serverConfigResource = useService(ServerConfigResource); const service = useService(ServerConfigurationService); const controller = useController(ServerConfigurationPageController); + const changed = serverConfigResource.isChanged() + || serverConfigResource.isNavigatorSettingsChanged(service.state.navigatorConfig); useFormValidator(service.validationTask, focusedRef); return styled(style)( {controller.editing && ( - - + + )} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 403643e9b1..6de9745314 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -167,7 +167,7 @@ export class ServerConfigurationService { } try { - await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: data.state.navigatorConfig }); + await this.serverConfigResource.setDefaultNavigatorSettings(data.state.navigatorConfig); if (!data.configurationWizard || data.finish) { await this.serverConfigResource.save(); } diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index a43564f9c8..234609abcb 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -11,7 +11,7 @@ import { AppScreenService } from '@cloudbeaver/core-app'; import { AppAuthService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; +import type { IExecutorHandler } from '@cloudbeaver/core-executor'; import { SessionDataResource } from '@cloudbeaver/core-root'; import { ScreenService } from '@cloudbeaver/core-routing'; import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; @@ -37,8 +37,8 @@ export class AuthenticationService extends Bootstrap { this.authPromise = null; } - async authUser(provider: string | null = null): Promise { - await this.auth(false, provider); + async authUser(provider: string | null = null, link?: boolean): Promise { + await this.auth(false, provider, link); } async logout(): Promise { @@ -53,11 +53,11 @@ export class AuthenticationService extends Bootstrap { } } - private async auth(persistent: boolean, provider: string | null = null) { + private async auth(persistent: boolean, provider: string | null = null, link?: boolean) { if (this.authPromise) { return this.authPromise; } - this.authPromise = this.authDialogService.showLoginForm(persistent, provider); + this.authPromise = this.authDialogService.showLoginForm(persistent, provider, link); try { await this.authPromise; } finally { @@ -75,9 +75,6 @@ export class AuthenticationService extends Bootstrap { } register(): void { - this.sessionDataResource.beforeLoad.addHandler( - ExecutorInterrupter.interrupter(() => this.appAuthService.isAuthNeeded()) - ); this.sessionDataResource.beforeLoad.addPostHandler(() => { this.requireAuthentication(); }); this.appScreenService.activation.addHandler(() => this.requireAuthentication()); diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx index ae831e8b57..72f309eb4d 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx @@ -85,20 +85,25 @@ const styles = composes( ` ); -export const AuthDialog: DialogComponent = observer(function AuthDialog({ - payload, +interface IAuthPayload { + provider: string | null; + link?: boolean; +} + +export const AuthDialog: DialogComponent = observer(function AuthDialog({ + payload: { provider, link }, options, rejectDialog, }) { const userInfo = useService(UserInfoResource); - const controller = useController(AuthDialogController, rejectDialog); + const controller = useController(AuthDialogController, link || false, rejectDialog); const translate = useTranslate(); - if (payload) { - controller.selectProvider(payload); + if (provider) { + controller.selectProvider(provider); } - const showTabs = !payload && controller.providers.length > 1; + const showTabs = !provider && controller.providers.length > 1; const additional = userInfo.data !== null && controller.provider?.id && !userInfo.hasToken(controller.provider?.id); diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts index 2eec317c51..eb38e982b1 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts @@ -33,6 +33,7 @@ export class AuthDialogController implements IInitializableController, IDestruct readonly error = new GQLErrorCatcher(); private isDistructed = false; + private link!: boolean; private close!: () => void; constructor( @@ -49,7 +50,8 @@ export class AuthDialogController implements IInitializableController, IDestruct }); } - init(onClose: () => void) { + init(link: boolean, onClose: () => void) { + this.link = link; this.close = onClose; this.loadProviders(); } @@ -65,7 +67,7 @@ export class AuthDialogController implements IInitializableController, IDestruct this.isAuthenticating = true; try { - await this.authInfoService.login(this.provider.id, this.credentials); + await this.authInfoService.login(this.provider.id, this.credentials, this.link); this.close(); } catch (exception) { if (!this.error.catch(exception) || this.isDistructed) { diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts index 018b1cfcff..d86acbe9c2 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts @@ -17,7 +17,7 @@ export class AuthDialogService { private commonDialogService: CommonDialogService ) { } - async showLoginForm(persistent = false, provider: string | null = null): Promise { - await this.commonDialogService.open(AuthDialog, provider, { persistent }); + async showLoginForm(persistent = false, provider: string | null = null, link?: boolean): Promise { + await this.commonDialogService.open(AuthDialog, { provider, link }, { persistent }); } } From 2a6d533c046691d16257bcd02ee1b9a188b23bd3 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Wed, 31 Mar 2021 19:43:20 +0300 Subject: [PATCH 56/75] fix(core-app): CB-847, CB-867, CB-858 grid sorting indication, product info logo, remove sandwich from log viewer --- .../core-app/public/icons/logo-secondary.svg | 16 +++++++ .../icons/logo-secondary_theme-dark.svg | 16 +++++++ .../LogViewTab/LogEntry/LogEntry.tsx | 40 +++++++++-------- .../TableColumnHeader/TableColumnHeader.tsx | 43 +++++++++++++------ .../plugin-product/src/ProductInfoDialog.tsx | 23 +++++----- 5 files changed, 95 insertions(+), 43 deletions(-) create mode 100644 webapp/packages/core-app/public/icons/logo-secondary.svg create mode 100644 webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg diff --git a/webapp/packages/core-app/public/icons/logo-secondary.svg b/webapp/packages/core-app/public/icons/logo-secondary.svg new file mode 100644 index 0000000000..693353bef4 --- /dev/null +++ b/webapp/packages/core-app/public/icons/logo-secondary.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg b/webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg new file mode 100644 index 0000000000..966881ade7 --- /dev/null +++ b/webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx index 94e4eb64d3..b1354eb727 100644 --- a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx +++ b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx @@ -9,9 +9,8 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { Icon } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; -import { useStyles } from '@cloudbeaver/core-theming'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; import type { ILogEntry } from '../ILogEntry'; import { LogEntryController } from './LogEntryController'; @@ -26,24 +25,14 @@ const style = css` align-items: center; } message { - flex: 1; word-break: break-word; white-space: nowrap; overflow: hidden; padding-right: 16px; text-overflow: ellipsis; } - - snack { - display: flex; - cursor: pointer; - } - - snack Icon { - display: block; - width: 16px; - height: 10px; - fill: rgba(255, 255, 255, 0); + fill { + flex: 1; } tr:hover Icon { @@ -51,21 +40,34 @@ const style = css` } `; +const messageWithDetailsStyles = composes( + css` + message { + composes: theme-text-primary from global; + } + `, + css` + message { + cursor: pointer; + &:hover { + opacity: 0.8; + } + } + `); + export const LogEntry = observer(function LogEntry({ item }: LogEntryProps) { const controller = useController(LogEntryController, item); - return styled(useStyles(style))( + return styled(useStyles(style, !!item.stackTrace && messageWithDetailsStyles))( {item.type} {item.time} - + {item.message} - - - + diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx index 4299f3ea6b..edfaf42ad6 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx @@ -13,6 +13,7 @@ import styled, { css, use } from 'reshadow'; import { StaticImage, Icon } from '@cloudbeaver/core-blocks'; import type { SqlResultSet } from '@cloudbeaver/core-sdk'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; import type { SortMode } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContext } from '../DataGridContext'; @@ -45,7 +46,7 @@ const headerStyles = css` flex-grow: 1; } - sort-icon { + sort-icons { margin-left: 4px; display: flex; padding: 2px 4px; @@ -57,25 +58,41 @@ const headerStyles = css` cursor: pointer; } - sort-icon > Icon { + sort-icons > SortIcon { width: 8px; - fill: #cbcbcb; + fill: currentColor !important; } - sort-icon > Icon:last-child { + sort-icons > SortIcon:last-child { transform: scaleY(-1); } - sort-icon > Icon[|active] { - fill: #338ECC; - } - sort-icon:hover > Icon { + sort-icons:hover > SortIcon { width: 9px; } - sort-icon[|disabled] { + sort-icons[|disabled] { opacity: 0.7; cursor: default; } `; +const activeSortIcon = composes( + css` + Icon { + composes: theme-text-primary from global; + } + ` +); + +interface ISortIconProps { + active: boolean; + className?: string; +} + +const SortIcon: React.FC = function SortIcon({ active, className }) { + return styled(useStyles(active && activeSortIcon))( + + ); +}; + function getColumn(colIdx: number, source: SqlResultSet) { return source.columns?.[colIdx]; } @@ -134,10 +151,10 @@ export const TableColumnHeader: React.FC> = observer(fu {columnName} {sortable && ( - - - - + + + + )} ); diff --git a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx index 0190a6485f..8942a81f7a 100644 --- a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx +++ b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx @@ -15,15 +15,9 @@ import { useService } from '@cloudbeaver/core-di'; import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs'; import { useTranslate } from '@cloudbeaver/core-localization'; import { ServerConfigResource } from '@cloudbeaver/core-root'; -import { composes, useStyles } from '@cloudbeaver/core-theming'; +import { ThemeService, useStyles } from '@cloudbeaver/core-theming'; -const productInfoDialogStyles = composes( - css` - IconOrImage { - composes: theme-background-primary from global; - } - `, - css` +const productInfoDialogStyles = css` CommonDialogWrapper { min-width: 600px; } @@ -38,20 +32,27 @@ const productInfoDialogStyles = composes( display: flex; white-space: pre-wrap; } + FormFieldDescriptionNew { + white-space: pre-wrap; + } IconOrImage { - padding: 10px; + max-width: 154px; + height: 32px; border-radius: 2px; } -`); +`; export const ProductInfoDialog: React.FC> = observer( function ProductInfoDialog(props) { const translate = useTranslate(); const styles = useStyles(BASE_CONTAINERS_STYLES, productInfoDialogStyles); const serverConfigResource = useService(ServerConfigResource); + const themeService = useService(ThemeService); + const version = useAppVersion(); const productInfo = serverConfigResource.data?.productInfo; + const logoIcon = themeService.currentThemeId === 'light' ? '/icons/logo-secondary.svg' : '/icons/logo-secondary_theme-dark.svg'; return styled(styles)( > = observer( {translate('app_product_info_placeholder')} ) : ( <> - + {productInfo.name} From 197c303f5b31760010ec9132db7cf66cc08aa246 Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 1 Apr 2021 11:10:15 +0300 Subject: [PATCH 57/75] chore: update README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index a8cc4417a6..7a35231288 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ You can see live demo of CloudBeaver here: https://demo.cloudbeaver.io ## Changelog +### CloudBeaver 20.0.6 - 2021-03-31 + +- Value panel for Data Viewer was added: a full text of your cell value on a special panel to view and edit. +- Spatial data support was added: single and multi objects can be shown on a map. +- Possibility to Show or Hide system objects was added to the context menu in the Navigator. +- All product information can be found in public settings. +- A lot of minor UI fixes and improvements. + ### CloudBeaver 20.0.5 - 2021-03-18 - New design for the connection form From d89097b2fe72a3828c160db4f4d9ef48ac6e7d64 Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 1 Apr 2021 12:34:33 +0300 Subject: [PATCH 58/75] fix: CB-792 app freezes --- webapp/packages/core-blocks/src/useDataResource.ts | 11 ++++++++++- webapp/packages/core-blocks/src/useMapResource.ts | 11 ++++++++++- .../core-executor/src/TaskScheduler/TaskScheduler.ts | 5 +++++ .../ServerConfigurationAuthenticationBootstrap.ts | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/webapp/packages/core-blocks/src/useDataResource.ts b/webapp/packages/core-blocks/src/useDataResource.ts index 768a1538ae..dc4c328dc0 100644 --- a/webapp/packages/core-blocks/src/useDataResource.ts +++ b/webapp/packages/core-blocks/src/useDataResource.ts @@ -51,6 +51,7 @@ export function useDataResource< const outdated = resource.isOutdated(key); const refObj = useObjectRef({ + loading: false, resource, key, exception, @@ -67,7 +68,13 @@ export function useDataResource< }); refObj.load = async function load() { - const { resource, actions, prevData } = refObj; + const { loading, resource, actions, prevData } = refObj; + + if (loading) { + return; + } + + this.loading = true; try { await actions?.onLoad?.(resource); @@ -91,6 +98,8 @@ export function useDataResource< setException(exception); actions?.onError?.(exception); notifications.logException(exception, 'Can\'t load data'); + } finally { + this.loading = false; } }; diff --git a/webapp/packages/core-blocks/src/useMapResource.ts b/webapp/packages/core-blocks/src/useMapResource.ts index 2176ef407d..c502e65d43 100644 --- a/webapp/packages/core-blocks/src/useMapResource.ts +++ b/webapp/packages/core-blocks/src/useMapResource.ts @@ -67,6 +67,7 @@ export function useMapResource< } const refObj = useObjectRef({ + loading: false, resource, key, exception, @@ -85,7 +86,13 @@ export function useMapResource< const outdated = resource.isOutdated(key); refObj.load = async function load() { - const { resource, actions, prevData } = refObj; + const { loading, resource, actions, prevData } = refObj; + + if (loading) { + return; + } + + this.loading = true; try { await actions?.onLoad?.(resource); @@ -109,6 +116,8 @@ export function useMapResource< setException(exception); actions?.onError?.(exception); notifications.logException(exception, 'Can\'t load data'); + } finally { + this.loading = false; } }; diff --git a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts index 5db20bb715..a9b860db49 100644 --- a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts +++ b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts @@ -12,6 +12,8 @@ import type { ITask } from './ITask'; export type BlockedExecution = (active: T, current: T) => boolean; +const queueLimit = 100; + export class TaskScheduler { get activeList(): TIdentifier[] { return this.queue.map(task => task.id); @@ -47,6 +49,9 @@ export class TaskScheduler { task: this.scheduler(id, promise), }; + if (this.queue.length > queueLimit) { + throw new Error('Execution queue limit is reached'); + } this.queue.push(task); try { diff --git a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts index dc41afccfe..dea87f8953 100644 --- a/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts @@ -56,7 +56,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { data.state.serverConfig.anonymousAccessEnabled = config.anonymousAccessEnabled; data.state.serverConfig.authenticationEnabled = config.authenticationEnabled; - data.state.serverConfig.enabledAuthProviders = config.enabledAuthProviders; + data.state.serverConfig.enabledAuthProviders = [...config.enabledAuthProviders]; } catch (exception) { ExecutorInterrupter.interrupt(contexts); this.notificationService.logException(exception, 'Can\'t load server configuration'); From 422dce28555ed450f4bcfffb1948c0434f39d882 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 12:48:04 +0300 Subject: [PATCH 59/75] fix(core-app): CB-847, CB-867, CB-858 naming correction --- ...y_theme-dark.svg => product-logo_dark.svg} | 0 ...o-secondary.svg => product-logo_light.svg} | 0 .../LogViewTab/LogEntry/LogEntry.tsx | 34 ++++++------------- .../plugin-product/src/ProductInfoDialog.tsx | 2 +- 4 files changed, 11 insertions(+), 25 deletions(-) rename webapp/packages/core-app/public/icons/{logo-secondary_theme-dark.svg => product-logo_dark.svg} (100%) rename webapp/packages/core-app/public/icons/{logo-secondary.svg => product-logo_light.svg} (100%) diff --git a/webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg b/webapp/packages/core-app/public/icons/product-logo_dark.svg similarity index 100% rename from webapp/packages/core-app/public/icons/logo-secondary_theme-dark.svg rename to webapp/packages/core-app/public/icons/product-logo_dark.svg diff --git a/webapp/packages/core-app/public/icons/logo-secondary.svg b/webapp/packages/core-app/public/icons/product-logo_light.svg similarity index 100% rename from webapp/packages/core-app/public/icons/logo-secondary.svg rename to webapp/packages/core-app/public/icons/product-logo_light.svg diff --git a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx index b1354eb727..5c506ba491 100644 --- a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx +++ b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx @@ -7,10 +7,11 @@ */ import { observer } from 'mobx-react-lite'; -import styled, { css } from 'reshadow'; +import styled, { css, use } from 'reshadow'; +import { Link } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; -import { composes, useStyles } from '@cloudbeaver/core-theming'; +import { useStyles } from '@cloudbeaver/core-theming'; import type { ILogEntry } from '../ILogEntry'; import { LogEntryController } from './LogEntryController'; @@ -30,9 +31,9 @@ const style = css` overflow: hidden; padding-right: 16px; text-overflow: ellipsis; - } - fill { - flex: 1; + &[|details] { + cursor: pointer; + } } tr:hover Icon { @@ -40,34 +41,19 @@ const style = css` } `; -const messageWithDetailsStyles = composes( - css` - message { - composes: theme-text-primary from global; - } - `, - css` - message { - cursor: pointer; - &:hover { - opacity: 0.8; - } - } - `); - export const LogEntry = observer(function LogEntry({ item }: LogEntryProps) { const controller = useController(LogEntryController, item); + const isDetails = !!item.stackTrace; - return styled(useStyles(style, !!item.stackTrace && messageWithDetailsStyles))( + return styled(useStyles(style))( {item.type} {item.time} - - {item.message} + + {isDetails ? {item.message} : item.message} - diff --git a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx index 8942a81f7a..c533b81b88 100644 --- a/webapp/packages/plugin-product/src/ProductInfoDialog.tsx +++ b/webapp/packages/plugin-product/src/ProductInfoDialog.tsx @@ -52,7 +52,7 @@ export const ProductInfoDialog: React.FC> = observer( const version = useAppVersion(); const productInfo = serverConfigResource.data?.productInfo; - const logoIcon = themeService.currentThemeId === 'light' ? '/icons/logo-secondary.svg' : '/icons/logo-secondary_theme-dark.svg'; + const logoIcon = themeService.currentThemeId === 'light' ? '/icons/product-logo_light.svg' : '/icons/product-logo_dark.svg'; return styled(styles)( Date: Thu, 1 Apr 2021 16:38:52 +0300 Subject: [PATCH 60/75] CB-797 Remove redundant property "authenticationEnabled" --- samples/sample-databases/cloudbeaver.conf | 1 - .../io.cloudbeaver.server/schema/service.core.graphqls | 2 +- .../src/io/cloudbeaver/model/WebServerConfig.java | 4 +++- .../src/io/cloudbeaver/server/CBAppConfig.java | 9 --------- .../src/io/cloudbeaver/server/CBApplication.java | 1 - .../schema/service.admin.graphqls | 1 - .../io/cloudbeaver/service/admin/AdminServerConfig.java | 6 ------ .../cloudbeaver/service/admin/impl/WebServiceAdmin.java | 1 - .../service/auth/impl/WebServiceAuthImpl.java | 6 ------ .../product/web-server/CloudbeaverServer.product.launch | 2 ++ 10 files changed, 6 insertions(+), 27 deletions(-) diff --git a/samples/sample-databases/cloudbeaver.conf b/samples/sample-databases/cloudbeaver.conf index 323bad43f6..cd1b803729 100644 --- a/samples/sample-databases/cloudbeaver.conf +++ b/samples/sample-databases/cloudbeaver.conf @@ -34,7 +34,6 @@ }, app: { anonymousAccessEnabled: true, - authenticationEnabled: true, anonymousUserRole: "user", supportsCustomConnections: false, diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index a29ca78deb..ea5ad8c523 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -91,7 +91,7 @@ type ServerConfig { workspaceId: ID! anonymousAccessEnabled: Boolean - authenticationEnabled: Boolean + authenticationEnabled: Boolean @deprecated supportsCustomConnections: Boolean supportsConnectionBrowser: Boolean supportsWorkspaces: 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 4a069950e2..b65aae1202 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 @@ -26,6 +26,7 @@ import org.jkiss.dbeaver.registry.language.PlatformLanguageDescriptor; import org.jkiss.dbeaver.registry.language.PlatformLanguageRegistry; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.dbeaver.utils.GeneralUtils; +import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import java.util.ArrayList; @@ -65,7 +66,8 @@ public class WebServerConfig { @Property public boolean isAuthenticationEnabled() { - return application.getAppConfiguration().isAuthenticationEnabled(); + String[] enabledAuthProviders = getEnabledAuthProviders(); + return enabledAuthProviders == null || !ArrayUtils.isEmpty(enabledAuthProviders); } @Property 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 2a5b8720db..649850ced5 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 @@ -33,7 +33,6 @@ public class CBAppConfig { public static final DataSourceNavigatorSettings DEFAULT_VIEW_SETTINGS = DataSourceNavigatorSettings.PRESET_FULL.getSettings(); private boolean anonymousAccessEnabled = true; - private boolean authenticationEnabled = true; private String anonymousUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME; private String defaultUserRole = CBConstants.DEFAUL_APP_ANONYMOUS_ROLE_NAME; private boolean supportsCustomConnections = true; @@ -48,14 +47,6 @@ public class CBAppConfig { private DataSourceNavigatorSettings defaultNavigatorSettings = DEFAULT_VIEW_SETTINGS; private Map plugins = new LinkedHashMap<>(); - public boolean isAuthenticationEnabled() { - return authenticationEnabled; - } - - public void setAuthenticationEnabled(boolean authenticationEnabled) { - this.authenticationEnabled = authenticationEnabled; - } - public boolean isAnonymousAccessEnabled() { return anonymousAccessEnabled; } 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 16f8d5e8d4..ce951b5766 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 @@ -591,7 +591,6 @@ public class CBApplication extends BaseApplicationImpl { json.name("app"); json.beginObject(); JSONUtils.field(json, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled()); - JSONUtils.field(json, "authenticationEnabled", appConfig.isAuthenticationEnabled()); JSONUtils.field(json, "supportsCustomConnections", appConfig.isSupportsCustomConnections()); JSONUtils.field(json, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled()); JSONUtils.field(json, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled()); 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 c3c02bbe76..a7ae507a25 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -52,7 +52,6 @@ input ServerConfigInput { adminPassword: String anonymousAccessEnabled: Boolean - authenticationEnabled: Boolean customConnectionsEnabled: Boolean publicCredentialsSaveEnabled: Boolean adminCredentialsSaveEnabled: Boolean 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 fb77d3d77a..db231e48ce 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 @@ -35,7 +35,6 @@ public class AdminServerConfig { private String adminPassword; private boolean anonymousAccessEnabled; - private boolean authenticationEnabled; private boolean customConnectionsEnabled; private boolean publicCredentialsSaveEnabled; private boolean adminCredentialsSaveEnabled; @@ -50,7 +49,6 @@ public class AdminServerConfig { CBAppConfig appConfig = CBApplication.getInstance().getAppConfiguration(); this.anonymousAccessEnabled = JSONUtils.getBoolean(params, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled()); - this.authenticationEnabled = JSONUtils.getBoolean(params, "authenticationEnabled", appConfig.isAuthenticationEnabled()); this.customConnectionsEnabled = JSONUtils.getBoolean(params, "customConnectionsEnabled", appConfig.isSupportsCustomConnections()); this.publicCredentialsSaveEnabled = JSONUtils.getBoolean(params, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled()); this.adminCredentialsSaveEnabled = JSONUtils.getBoolean(params, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled()); @@ -91,10 +89,6 @@ public class AdminServerConfig { return anonymousAccessEnabled; } - public boolean isAuthenticationEnabled() { - return authenticationEnabled; - } - public boolean isCustomConnectionsEnabled() { return customConnectionsEnabled; } 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 b5066b1073..8e1b10f27c 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 @@ -354,7 +354,6 @@ public class WebServiceAdmin implements DBWServiceAdmin { try { CBAppConfig appConfig = new CBAppConfig(); appConfig.setAnonymousAccessEnabled(config.isAnonymousAccessEnabled()); - appConfig.setAuthenticationEnabled(config.isAuthenticationEnabled()); appConfig.setSupportsCustomConnections(config.isCustomConnectionsEnabled()); appConfig.setPublicCredentialsSaveEnabled(config.isPublicCredentialsSaveEnabled()); appConfig.setAdminCredentialsSaveEnabled(config.isAdminCredentialsSaveEnabled()); diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index a2dea32a28..025ac157ff 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -154,12 +154,6 @@ public class WebServiceAuthImpl implements DBWServiceAuth { throw new DBCException("You cannot authorize with different users credentials"); } - // Check for auth enabled. Auth is always enabled for admins - if (!CBApplication.getInstance().getAppConfiguration().isAuthenticationEnabled()) { - if (!securityController.getUserPermissions(userId).contains(DBWConstants.PERMISSION_ADMIN)) { - throw new DBWebException("Authentication was disabled for this server"); - } - } user = curUser; } if (user == null) { diff --git a/server/product/web-server/CloudbeaverServer.product.launch b/server/product/web-server/CloudbeaverServer.product.launch index 7058163bf8..21cff39161 100644 --- a/server/product/web-server/CloudbeaverServer.product.launch +++ b/server/product/web-server/CloudbeaverServer.product.launch @@ -51,10 +51,12 @@ + + From 93f213ff96c768a148f1bfa21b27c59da54104a0 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 1 Apr 2021 16:41:43 +0300 Subject: [PATCH 61/75] #11379 Value proposal from linked dictionary + PG dictionary columns detection fixed --- .../io.cloudbeaver.service.admin/schema/service.admin.graphqls | 1 + 1 file changed, 1 insertion(+) 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 a7ae507a25..616371c39c 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -52,6 +52,7 @@ input ServerConfigInput { adminPassword: String anonymousAccessEnabled: Boolean + authenticationEnabled: Boolean @deprecated customConnectionsEnabled: Boolean publicCredentialsSaveEnabled: Boolean adminCredentialsSaveEnabled: Boolean From 4c67d398a7890adba01fdb92037998049206d284 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 1 Apr 2021 16:42:30 +0300 Subject: [PATCH 62/75] CB-797 Remove redundant property "authenticationEnabled" --- .../io.cloudbeaver.service.admin/schema/service.admin.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 616371c39c..c3c02bbe76 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -52,7 +52,7 @@ input ServerConfigInput { adminPassword: String anonymousAccessEnabled: Boolean - authenticationEnabled: Boolean @deprecated + authenticationEnabled: Boolean customConnectionsEnabled: Boolean publicCredentialsSaveEnabled: Boolean adminCredentialsSaveEnabled: Boolean From 6a1971ef7f268f7a2823aac42dabb004b7a29d17 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 16:53:44 +0300 Subject: [PATCH 63/75] fix(core-app): CB-911 add catch error block for delete and refresh node operations --- .../NavigationTreeContextMenuService.ts | 8 ++++++-- .../NodesManager/ConnectionDialogsService.ts | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index c76e3a614c..a4101e7d7d 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -177,9 +177,13 @@ export class NavigationTreeContextMenuService extends Bootstrap { }, order: Number.MAX_SAFE_INTEGER, title: 'app_navigationTree_refreshNode', - onClick: context => { + onClick: async context => { const node = context.data; - this.navNodeManagerService.refreshTree(node.id); + try { + await this.navNodeManagerService.refreshTree(node.id); + } catch (exception) { + this.notificationService.logException(exception, 'Failed to refresh node'); + } }, } ); diff --git a/webapp/packages/core-app/src/shared/NodesManager/ConnectionDialogsService.ts b/webapp/packages/core-app/src/shared/NodesManager/ConnectionDialogsService.ts index b80fa2390b..f2943953f6 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/ConnectionDialogsService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/ConnectionDialogsService.ts @@ -9,6 +9,7 @@ import { ConnectionInfoResource, ConnectionsManagerService, EConnectionFeature } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ContextMenuService, IMenuContext } from '@cloudbeaver/core-dialogs'; +import { NotificationService } from '@cloudbeaver/core-events'; import { NavigationTreeContextMenuService } from '../../NavigationTree/NavigationTreeContextMenuService'; import { EMainMenu, MainMenuService } from '../../TopNavBar/MainMenu/MainMenuService'; @@ -22,7 +23,8 @@ export class ConnectionDialogsService extends Bootstrap { private mainMenuService: MainMenuService, private contextMenuService: ContextMenuService, private connectionsManagerService: ConnectionsManagerService, - private connectionInfoResource: ConnectionInfoResource + private connectionInfoResource: ConnectionInfoResource, + private notificationService: NotificationService, ) { super(); } @@ -76,15 +78,19 @@ export class ConnectionDialogsService extends Bootstrap { || !connection?.features.includes(EConnectionFeature.manageable); }, title: 'ui_delete', - onClick: (context: IMenuContext) => { + onClick: async (context: IMenuContext) => { const node = context.data; - this.connectionsManagerService.deleteConnection( - NodeManagerUtils.connectionNodeIdToConnectionId(node.id) - ); + try { + await this.connectionsManagerService.deleteConnection( + NodeManagerUtils.connectionNodeIdToConnectionId(node.id) + ); + } catch (exception) { + this.notificationService.logException(exception, 'Failed to delete connection'); + } }, } ); } - load(): void {} + load(): void { } } From 41895a70be6efcd709ea0eb750d18ca906909a9f Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 1 Apr 2021 17:06:08 +0300 Subject: [PATCH 64/75] CB-626 Log errors in session logger --- .../cloudbeaver/model/WebServerMessage.java | 23 ++++++++- .../cloudbeaver/model/session/WebSession.java | 47 ++++++++++++------- .../server/graphql/GraphQLEndpoint.java | 10 +++- .../service/WebServiceBindingBase.java | 10 ++-- 4 files changed, 65 insertions(+), 25 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerMessage.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerMessage.java index 797a03651d..ddb27f97c9 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerMessage.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/WebServerMessage.java @@ -18,6 +18,9 @@ package io.cloudbeaver.model; import io.cloudbeaver.server.CBConstants; +import java.io.PrintWriter; +import java.io.StringWriter; + /** * Web server message */ @@ -32,13 +35,24 @@ public class WebServerMessage { } private final MessageType type; - private long time; - private String message; + private final long time; + private final String message; + private final Throwable error; + public WebServerMessage(MessageType type, String message) { + this(type, message, null); + } + + public WebServerMessage(Throwable error) { + this(MessageType.ERROR, error.getMessage(), error); + } + + public WebServerMessage(MessageType type, String message, Throwable error) { this.type = type; this.time = System.currentTimeMillis(); this.message = message; + this.error = error; } public MessageType getType() { @@ -54,6 +68,11 @@ public class WebServerMessage { } public String getStackTrace() { + if (error != null) { + StringWriter buf = new StringWriter(); + error.printStackTrace(new PrintWriter(buf, true)); + return buf.toString(); + } return null; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java index aaceb14ae1..b64de35a17 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/model/session/WebSession.java @@ -92,7 +92,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap private boolean cacheExpired; private final Map connections = new HashMap<>(); - private final List progressMessages = new ArrayList<>(); + private final List sessionMessages = new ArrayList<>(); private final Map asyncTasks = new HashMap<>(); private final Map attributes = new HashMap<>(); @@ -101,7 +101,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap private final List authTokens = new ArrayList<>(); private DBNModel navigatorModel; - private DBRProgressMonitor progressMonitor = new SessionProgressMonitor(); + private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor(); private ProjectMetadata sessionProject; private final SessionContextImpl sessionAuthContext; @@ -206,6 +206,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap try { resetSessionCache(); } catch (DBCException e) { + addSessionError(e); log.error(e); } } @@ -274,6 +275,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap try { this.refreshConnections(); } catch (Exception e) { + addSessionError(e); log.error("Error getting connection list", e); } } @@ -320,6 +322,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap .getSubjectConnectionAccess(new String[]{subjectId})) .map(DBWConnectionGrant::getConnectionId).collect(Collectors.toSet()); } catch (DBCException e) { + addSessionError(e); log.error("Error reading connection grants", e); return Collections.emptySet(); } @@ -374,6 +377,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap accessibleConnectionIds = readAccessibleConnectionIds(); } catch (Exception e) { + addSessionError(e); log.error("Error reading session permissions", e); } } @@ -395,10 +399,10 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap * Returns and clears progress messages */ @Association - public List getProgressMessages() { - synchronized (progressMessages) { - List copy = new ArrayList<>(progressMessages); - progressMessages.clear(); + public List getSessionMessages() { + synchronized (sessionMessages) { + List copy = new ArrayList<>(sessionMessages); + sessionMessages.clear(); return copy; } } @@ -423,6 +427,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap } } } catch (Exception e) { + addSessionError(e); log.error("Error persisting web session", e); } } @@ -547,7 +552,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap return true; } - public WebAsyncTaskInfo createAndRunAsyncTask(String taskName, WebAsyncTaskProcessor runnable) { + public WebAsyncTaskInfo createAndRunAsyncTask(String taskName, WebAsyncTaskProcessor runnable) { int taskId = TASK_ID.incrementAndGet(); WebAsyncTaskInfo asyncTask = getAsyncTask(String.valueOf(taskId), taskName, true); @@ -562,6 +567,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap asyncTask.setStatus("Finished"); asyncTask.setRunning(false); } catch (InvocationTargetException e) { + addSessionError(e.getTargetException()); asyncTask.setJobError(e.getTargetException()); } catch (InterruptedException e) { asyncTask.setJobError(e); @@ -576,19 +582,25 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap return asyncTask; } + public void addSessionError(Throwable exception) { + synchronized (sessionMessages) { + sessionMessages.add(new WebServerMessage(exception)); + } + } + public List readLog(Integer maxEntries, Boolean clearLog) { - synchronized (progressMessages) { + synchronized (sessionMessages) { List messages = new ArrayList<>(); int entryCount = CommonUtils.toInt(maxEntries); - if (entryCount == 0 || entryCount >= progressMessages.size()) { - messages.addAll(progressMessages); + if (entryCount == 0 || entryCount >= sessionMessages.size()) { + messages.addAll(sessionMessages); if (CommonUtils.toBoolean(clearLog)) { - progressMessages.clear(); + sessionMessages.clear(); } } else { - messages.addAll(progressMessages.subList(0, maxEntries)); + messages.addAll(sessionMessages.subList(0, maxEntries)); if (CommonUtils.toBoolean(clearLog)) { - progressMessages.removeAll(messages); + sessionMessages.removeAll(messages); } } return messages; @@ -747,6 +759,7 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap // credGson.fromJson(credGson.toJsonTree(authProperties), credentials.getClass()); // configuration.getAuthModel().saveCredentials(dataSourceContainer, configuration, credentials); } catch (DBException e) { + addSessionError(e); log.error(e); } return true; @@ -773,15 +786,15 @@ public class WebSession implements DBASession, DBAAuthCredentialsProvider, IAdap private class SessionProgressMonitor extends BaseProgressMonitor { @Override public void beginTask(String name, int totalWork) { - synchronized (progressMessages) { - progressMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name)); + synchronized (sessionMessages) { + sessionMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name)); } } @Override public void subTask(String name) { - synchronized (progressMessages) { - progressMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name)); + synchronized (sessionMessages) { + sessionMessages.add(new WebServerMessage(WebServerMessage.MessageType.INFO, name)); } } } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java index 52e3ab602e..ef85af5748 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/graphql/GraphQLEndpoint.java @@ -33,9 +33,11 @@ import graphql.schema.idl.SchemaParser; import graphql.schema.idl.TypeDefinitionRegistry; import io.cloudbeaver.DBWebException; import io.cloudbeaver.WebServiceUtils; +import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.registry.WebServiceRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.DBWServiceBindingGraphQL; +import io.cloudbeaver.service.WebServiceBindingBase; import org.jkiss.dbeaver.Log; import org.jkiss.utils.IOUtils; @@ -256,7 +258,7 @@ public class GraphQLEndpoint extends HttpServlet { response.getWriter().print(resString); } - private class WebInstrumentation extends SimpleInstrumentation { + private static class WebInstrumentation extends SimpleInstrumentation { @Override public CompletableFuture instrumentExecutionResult(ExecutionResult executionResult, InstrumentationExecutionParameters parameters) { return super.instrumentExecutionResult(executionResult, parameters); @@ -285,6 +287,12 @@ public class GraphQLEndpoint extends HttpServlet { } log.debug("GraphQL call failed at '" + handlerParameters.getPath() + "', " + handlerParameters.getArgumentValues(), exception); + // Log in session + WebSession webSession = WebServiceBindingBase.findWebSession(handlerParameters.getDataFetchingEnvironment()); + if (webSession != null) { + webSession.addSessionError(exception); + } + SourceLocation sourceLocation = handlerParameters.getSourceLocation(); ExecutionPath path = handlerParameters.getPath(); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index de1c9ca0ab..62f8d96611 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -106,16 +106,16 @@ public abstract class WebServiceBindingBase impleme getServletRequest(env), errorOnNotFound); } - protected static WebSession findWebSession(DataFetchingEnvironment env) { - return CBPlatform.getInstance().getSessionManager().findWebSession( - getServletRequest(env)); - } - @NotNull protected static WebConnectionInfo getWebConnection(DataFetchingEnvironment env) throws DBWebException { return getWebConnection(getWebSession(env), env.getArgument("connectionId")); } + public static WebSession findWebSession(DataFetchingEnvironment env) { + return CBPlatform.getInstance().getSessionManager().findWebSession( + getServletRequest(env)); + } + @NotNull public static WebConnectionInfo getWebConnection(WebSession session, String connectionId) throws DBWebException { return session.getWebConnectionInfo(connectionId); From 053930f9619814c3d98a0a9f11a8f5ba0ddd6a7f Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 1 Apr 2021 17:10:43 +0300 Subject: [PATCH 65/75] CB-883 Session expire with open log editor fix --- .../src/io/cloudbeaver/service/core/WebServiceBindingCore.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java index 39a01f4ad0..88e813aaa1 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java @@ -72,7 +72,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase return Collections.emptyList(); } return getService(env).readSessionLog( - getWebSession(env), + session, env.getArgument("maxEntries"), env.getArgument("clearEntries")); }) From da6d439f7483ae7b66934a555fcbcf29ccdba15a Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 1 Apr 2021 17:14:57 +0300 Subject: [PATCH 66/75] CB-918 Connection name for template connections --- .../schema/service.core.graphqls | 2 +- .../service/core/DBWServiceCore.java | 14 +++++++++----- .../service/core/WebServiceBindingCore.java | 5 ++++- .../service/core/impl/WebServiceCore.java | 17 ++++++++++++----- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index ea5ad8c523..5425bb9bca 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -433,7 +433,7 @@ extend type Mutation { deleteConnection( id: ID! ): Boolean! - createConnectionFromTemplate( templateId: ID! ): ConnectionInfo! + createConnectionFromTemplate( templateId: ID!, connectionName: String ): ConnectionInfo! # Copies connection configuration from node copyConnectionFromNode( nodePath: String!, config: ConnectionConfig ): ConnectionInfo! diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java index f3469a82e0..a355201a6e 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java @@ -115,21 +115,25 @@ public interface DBWServiceCore extends DBWService { @NotNull WebConnectionConfig connectionConfig) throws DBWebException; @WebAction - boolean deleteConnection(WebSession webSession, String connectionId) throws DBWebException; + boolean deleteConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException; @WebAction - WebConnectionInfo createConnectionFromTemplate(WebSession webSession, String templateId) throws DBWebException; + WebConnectionInfo createConnectionFromTemplate( + @NotNull WebSession webSession, + @NotNull String templateId, + @Nullable String connectionName) throws DBWebException; + @WebAction() WebConnectionInfo copyConnectionFromNode(@NotNull WebSession webSession, @NotNull String nodePath, @NotNull WebConnectionConfig config) throws DBWebException; @WebAction - WebConnectionInfo testConnection(WebSession webSession, WebConnectionConfig connectionConfig) throws DBWebException; + WebConnectionInfo testConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig connectionConfig) throws DBWebException; @WebAction - WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException; + WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException; @WebAction - WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException; + WebConnectionInfo closeConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException; /////////////////////////////////////////// // Navigator settings diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java index 88e813aaa1..479ec91de5 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/WebServiceBindingCore.java @@ -89,7 +89,10 @@ public class WebServiceBindingCore extends WebServiceBindingBase .dataFetcher("createConnection", env -> getService(env).createConnection(getWebSession(env), getConnectionConfig(env))) .dataFetcher("updateConnection", env -> getService(env).updateConnection(getWebSession(env), getConnectionConfig(env))) .dataFetcher("deleteConnection", env -> getService(env).deleteConnection(getWebSession(env), env.getArgument("id"))) - .dataFetcher("createConnectionFromTemplate", env -> getService(env).createConnectionFromTemplate(getWebSession(env), env.getArgument("templateId"))) + .dataFetcher("createConnectionFromTemplate", env -> getService(env).createConnectionFromTemplate( + getWebSession(env), + env.getArgument("templateId"), + env.getArgument("connectionName"))) .dataFetcher("copyConnectionFromNode", env -> getService(env).copyConnectionFromNode( getWebSession(env), env.getArgument("nodePath"), diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index e97e4da307..6ee0eb2a53 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -344,7 +344,7 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public boolean deleteConnection(WebSession webSession, String connectionId) throws DBWebException { + public boolean deleteConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException { if (!CBApplication.getInstance().getAppConfiguration().isSupportsCustomConnections()) { throw new DBWebException("Connection delete is restricted by server configuration"); } @@ -353,7 +353,11 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public WebConnectionInfo createConnectionFromTemplate(WebSession webSession, String templateId) throws DBWebException { + public WebConnectionInfo createConnectionFromTemplate( + @NotNull WebSession webSession, + @NotNull String templateId, + @Nullable String connectionName) throws DBWebException + { DBPDataSourceRegistry templateRegistry = WebServiceUtils.getGlobalDataSourceRegistry(); DBPDataSourceContainer dataSourceTemplate = templateRegistry.getDataSource(templateId); if (dataSourceTemplate == null) { @@ -364,6 +368,9 @@ public class WebServiceCore implements DBWServiceCore { DBPDataSourceContainer newDataSource = sessionRegistry.createDataSource(dataSourceTemplate); ((DataSourceDescriptor) newDataSource).setNavigatorSettings(CBApplication.getInstance().getAppConfiguration().getDefaultNavigatorSettings()); + if (!CommonUtils.isEmpty(connectionName)) { + newDataSource.setName(connectionName); + } sessionRegistry.addDataSource(newDataSource); WebConnectionInfo connectionInfo = new WebConnectionInfo(webSession, newDataSource); @@ -408,7 +415,7 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public WebConnectionInfo testConnection(WebSession webSession, WebConnectionConfig connectionConfig) throws DBWebException { + public WebConnectionInfo testConnection(@NotNull WebSession webSession, @NotNull WebConnectionConfig connectionConfig) throws DBWebException { String connectionId = connectionConfig.getConnectionId(); connectionConfig.setSaveCredentials(true); // It is used in createConnectionFromConfig @@ -444,7 +451,7 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException { + public WebNetworkEndpointInfo testNetworkHandler(@NotNull WebSession webSession, @NotNull WebNetworkHandlerConfigInput nhConfig) throws DBWebException { DBRProgressMonitor monitor = webSession.getProgressMonitor(); monitor.beginTask("Instantiate SSH tunnel", 2); @@ -497,7 +504,7 @@ public class WebServiceCore implements DBWServiceCore { } @Override - public WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException { + public WebConnectionInfo closeConnection(@NotNull WebSession webSession, @NotNull String connectionId) throws DBWebException { return closeAndDeleteConnection(webSession, connectionId, false); } From 1cd93bee58ae0139c0acdafde769ba35dcd984f2 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 18:00:49 +0300 Subject: [PATCH 67/75] fix(core-app): CB-898 children limit for all nodes in app --- webapp/packages/core-app/src/CoreSettingsService.ts | 3 ++- .../src/NavigationTree/NavigationTreeService.ts | 9 ++------- .../src/shared/NodesManager/NavTreeResource.ts | 13 ++++++++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/webapp/packages/core-app/src/CoreSettingsService.ts b/webapp/packages/core-app/src/CoreSettingsService.ts index a6abacd327..6e3c90a1f0 100644 --- a/webapp/packages/core-app/src/CoreSettingsService.ts +++ b/webapp/packages/core-app/src/CoreSettingsService.ts @@ -14,7 +14,8 @@ const defaultSettings = { 'app.logViewer.maxLogRecords': 1000, 'app.logViewer.logBatchSize': 2000, 'app.logViewer.maxFailedRequests': 3, - 'app.navigationTree.childrenLimit': 500, + // temporary limit for all nodes children in app + 'app.childrenLimit': 500, }; export type CoreSettings = typeof defaultSettings; diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts index f4eaffc6bb..97722ab392 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { action, computed, makeObservable } from 'mobx'; +import { action, makeObservable } from 'mobx'; import { ConnectionAuthService } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; @@ -39,10 +39,6 @@ export class NavigationTreeService { readonly navigationTreeMetadata: MetadataMap; readonly nodeSelectionTask: IExecutor; - get childrenLimit(): number { - return this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit'); - } - constructor( private navNodeManagerService: NavNodeManagerService, private notificationService: NotificationService, @@ -53,7 +49,6 @@ export class NavigationTreeService { private navNodeInfoResource: NavNodeInfoResource ) { makeObservable(this, { - childrenLimit: computed, unselectAll: action, }); @@ -80,7 +75,7 @@ export class NavigationTreeService { const children = this.navTreeResource.get(id); if (children) { - return children.slice(0, this.childrenLimit); + return children.slice(0, this.navTreeResource.childrenLimit); } return children; diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index 5681024ec9..ad1115a6bb 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { action, makeObservable } from 'mobx'; +import { action, computed, makeObservable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { @@ -22,6 +22,7 @@ import { } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; +import { CoreSettingsService } from '../../CoreSettingsService'; import { NavNodeInfoResource } from './NavNodeInfoResource'; // TODO: so much dirty @@ -39,13 +40,19 @@ interface INodeMetadata extends ICachedMapResourceMetadata { export class NavTreeResource extends CachedMapResource { protected metadata: MetadataMap; + get childrenLimit(): number { + return this.coreSettingsService.settings.getValue('app.childrenLimit'); + } + constructor( private graphQLService: GraphQLService, - private navNodeInfoResource: NavNodeInfoResource + private navNodeInfoResource: NavNodeInfoResource, + private coreSettingsService: CoreSettingsService, ) { super(); makeObservable(this, { + childrenLimit: computed, setDetails: action, }); @@ -242,6 +249,6 @@ export class NavTreeResource extends CachedMapResource { withDetails: metadata.withDetails, }); - return { navNodeChildren, navNodeInfo, parentPath }; + return { navNodeChildren: navNodeChildren.slice(0, this.childrenLimit), navNodeInfo, parentPath }; } } From d1343be9e1ecaf0ccd74be171c6c051911157b9d Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 18:37:18 +0300 Subject: [PATCH 68/75] fix(plugin-connection-template): CB-910 unique connection name for template connection --- .../core-connections/src/ConnectionInfoResource.ts | 3 ++- .../connections/createConnectionFromTemplate.gql | 3 ++- webapp/packages/core-sdk/src/sdk.ts | 10 ++++++++-- .../src/ConnectionDialog/ConnectionController.ts | 7 +++++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index f8f2b9b360..8c2e79b403 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -120,9 +120,10 @@ export class ConnectionInfoResource extends CachedMapResource { + async createFromTemplate(templateId: string, connectionName: string): Promise { const { connection } = await this.graphQLService.sdk.createConnectionFromTemplate({ templateId, + connectionName, ...this.getDefaultIncludes(), ...this.getIncludesMap(), }); diff --git a/webapp/packages/core-sdk/src/queries/connections/createConnectionFromTemplate.gql b/webapp/packages/core-sdk/src/queries/connections/createConnectionFromTemplate.gql index 6abc3245df..ce3efeec5f 100644 --- a/webapp/packages/core-sdk/src/queries/connections/createConnectionFromTemplate.gql +++ b/webapp/packages/core-sdk/src/queries/connections/createConnectionFromTemplate.gql @@ -1,12 +1,13 @@ mutation createConnectionFromTemplate( $templateId: ID!, + $connectionName: String!, $includeOrigin: Boolean!, $customIncludeOriginDetails: Boolean!, $includeAuthProperties: Boolean!, $customIncludeNetworkHandlerCredentials: Boolean! ) { - connection: createConnectionFromTemplate(templateId: $templateId) { + connection: createConnectionFromTemplate(templateId: $templateId, connectionName: $connectionName) { ...DatabaseConnection } } diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 1dde80cd51..0e42946493 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -341,6 +341,7 @@ export interface MutationCreateConnectionArgs { export interface MutationCreateConnectionFromTemplateArgs { templateId: Scalars['ID']; + connectionName?: Maybe; } export interface MutationDeleteConnectionArgs { @@ -480,6 +481,7 @@ export interface ServerConfig { version: Scalars['String']; workspaceId: Scalars['ID']; anonymousAccessEnabled?: Maybe; + /** @deprecated Field no longer supported */ authenticationEnabled?: Maybe; supportsCustomConnections?: Maybe; supportsConnectionBrowser?: Maybe; @@ -1166,6 +1168,7 @@ export interface CreateConnectionFromNodeMutation { connection: DatabaseConnecti export type CreateConnectionFromTemplateMutationVariables = Exact<{ templateId: Scalars['ID']; + connectionName: Scalars['String']; includeOrigin: Scalars['Boolean']; customIncludeOriginDetails: Scalars['Boolean']; includeAuthProperties: Scalars['Boolean']; @@ -2023,8 +2026,11 @@ export const CreateConnectionFromNodeDocument = ` } ${DatabaseConnectionFragmentDoc}`; export const CreateConnectionFromTemplateDocument = ` - mutation createConnectionFromTemplate($templateId: ID!, $includeOrigin: Boolean!, $customIncludeOriginDetails: Boolean!, $includeAuthProperties: Boolean!, $customIncludeNetworkHandlerCredentials: Boolean!) { - connection: createConnectionFromTemplate(templateId: $templateId) { + mutation createConnectionFromTemplate($templateId: ID!, $connectionName: String!, $includeOrigin: Boolean!, $customIncludeOriginDetails: Boolean!, $includeAuthProperties: Boolean!, $customIncludeNetworkHandlerCredentials: Boolean!) { + connection: createConnectionFromTemplate( + templateId: $templateId + connectionName: $connectionName + ) { ...DatabaseConnection } } diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index ae33144f00..4c255c9608 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -9,7 +9,7 @@ import { observable, makeObservable } from 'mobx'; import { - DBDriverResource, Connection, DatabaseAuthModelsResource, ConnectionInfoResource, DBDriver, ConnectionInitConfig + DBDriverResource, Connection, DatabaseAuthModelsResource, ConnectionInfoResource, DBDriver, ConnectionInitConfig, getUniqueConnectionName } from '@cloudbeaver/core-connections'; import type { IFormInitConfig } from '@cloudbeaver/core-connections'; import { injectable, IInitializableController, IDestructibleController } from '@cloudbeaver/core-di'; @@ -114,7 +114,10 @@ export class ConnectionController this.isConnecting = true; this.clearError(); try { - const connection = await this.connectionInfoResource.createFromTemplate(this.template.id); + const connectionNames = Array.from(this.connectionInfoResource.data.values()) + .map(connection => connection.name); + const uniqueConnectionName = getUniqueConnectionName(this.template.name || 'Template connection', connectionNames); + const connection = await this.connectionInfoResource.createFromTemplate(this.template.id, uniqueConnectionName); try { await this.connectionInfoResource.init(this.getConfig(connection.id)); From 0abbc538e49271cf94cd3eb09a8f781fb6bf32b3 Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Thu, 1 Apr 2021 19:11:41 +0300 Subject: [PATCH 69/75] dbeaver/dbeaver#10858 Retrieve rule manager from the completion context --- .../cloudbeaver/service/sql/WebSQLCompletionContext.java | 6 ++++++ .../src/io/cloudbeaver/service/sql/WebSQLProcessor.java | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java index 5914ced9c1..81df579789 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLCompletionContext.java @@ -25,6 +25,7 @@ import org.jkiss.dbeaver.model.sql.SQLSyntaxManager; import org.jkiss.dbeaver.model.sql.completion.SQLCompletionContext; import org.jkiss.dbeaver.model.sql.completion.SQLCompletionProposalBase; import org.jkiss.dbeaver.model.sql.completion.SQLCompletionRequest; +import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager; import java.util.Map; @@ -58,6 +59,11 @@ public class WebSQLCompletionContext implements SQLCompletionContext { return sqlContext.getProcessor().getSyntaxManager(); } + @Override + public SQLRuleManager getRuleManager() { + return sqlContext.getProcessor().getRuleManager(); + } + @Override public boolean isUseFQNames() { return false; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java index 029aff7f51..25fbfee929 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java @@ -37,6 +37,7 @@ import org.jkiss.dbeaver.model.navigator.DBNNode; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.SQLQuery; import org.jkiss.dbeaver.model.sql.SQLSyntaxManager; +import org.jkiss.dbeaver.model.sql.parser.SQLRuleManager; import org.jkiss.dbeaver.model.struct.*; import org.jkiss.utils.CommonUtils; @@ -56,6 +57,7 @@ public class WebSQLProcessor { private final WebSession webSession; private final WebConnectionInfo connection; private final SQLSyntaxManager syntaxManager; + private final SQLRuleManager ruleManager; private final Map contexts = new LinkedHashMap<>(); private AtomicInteger contextId = new AtomicInteger(); @@ -66,6 +68,9 @@ public class WebSQLProcessor { syntaxManager = new SQLSyntaxManager(); syntaxManager.init(connection.getDataSource()); + + ruleManager = new SQLRuleManager(syntaxManager); + ruleManager.loadRules(connection.getDataSource(), false); } void dispose() { @@ -87,6 +92,10 @@ public class WebSQLProcessor { return syntaxManager; } + SQLRuleManager getRuleManager() { + return ruleManager; + } + DBCExecutionContext getExecutionContext() { return DBUtils.getDefaultContext(connection.getDataSource(), false); } From 906950fbe9755de069b9cd9b4d94b612b25fea22 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 19:26:27 +0300 Subject: [PATCH 70/75] fix(core-blocks): CB-451 add missing tooltips --- webapp/packages/core-blocks/src/FormControls/InputField.tsx | 2 +- webapp/packages/core-blocks/src/FormControls/InputFieldNew.tsx | 2 +- .../src/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew.tsx | 1 + .../ServerConfiguration/Form/ServerConfigurationInfoForm.tsx | 1 + webapp/packages/plugin-administration/src/locales/en.ts | 1 + webapp/packages/plugin-administration/src/locales/ru.ts | 1 + .../src/Dialog/AuthProviderForm/AuthProviderForm.tsx | 1 + .../src/ObjectPropertiesPage/ObjectInfoTab/ObjectProperty.tsx | 2 ++ 8 files changed, 9 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-blocks/src/FormControls/InputField.tsx b/webapp/packages/core-blocks/src/FormControls/InputField.tsx index 4f4eb8e869..5cda6bd2c6 100644 --- a/webapp/packages/core-blocks/src/FormControls/InputField.tsx +++ b/webapp/packages/core-blocks/src/FormControls/InputField.tsx @@ -84,7 +84,7 @@ export const InputField: InputFieldType = observer(function InputField({ return styled(styles)( - {children} {required && '*'} + {children} {required && '*'} - {children}{required && ' *'} + {children}{required && ' *'} = observer(function RenderField({ return ( = observer(function Se {translate('administration_configuration_wizard_configuration_server_name')} parameter.user && ( Date: Thu, 1 Apr 2021 20:25:32 +0300 Subject: [PATCH 71/75] fix(plugin-connection-template): CB-910 data access syntax --- .../src/ConnectionDialog/ConnectionController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index 4c255c9608..84c8dcc73a 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -114,7 +114,7 @@ export class ConnectionController this.isConnecting = true; this.clearError(); try { - const connectionNames = Array.from(this.connectionInfoResource.data.values()) + const connectionNames = Array.from(this.connectionInfoResource.values) .map(connection => connection.name); const uniqueConnectionName = getUniqueConnectionName(this.template.name || 'Template connection', connectionNames); const connection = await this.connectionInfoResource.createFromTemplate(this.template.id, uniqueConnectionName); From 3343bba7484bd13deb7e4fa842be72e579c8277f Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 1 Apr 2021 20:31:01 +0300 Subject: [PATCH 72/75] fix(plugin-connection-template): CB-910 remove unnecessary code --- .../src/ConnectionDialog/ConnectionController.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts index 84c8dcc73a..29956bda46 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionController.ts @@ -114,8 +114,7 @@ export class ConnectionController this.isConnecting = true; this.clearError(); try { - const connectionNames = Array.from(this.connectionInfoResource.values) - .map(connection => connection.name); + const connectionNames = this.connectionInfoResource.values.map(connection => connection.name); const uniqueConnectionName = getUniqueConnectionName(this.template.name || 'Template connection', connectionNames); const connection = await this.connectionInfoResource.createFromTemplate(this.template.id, uniqueConnectionName); From a8efae88a8020a98cfd70a48d8ffc8d2cef79773 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 2 Apr 2021 12:01:24 +0300 Subject: [PATCH 73/75] fix(core-app): CB-898 remove unnecessary code --- webapp/packages/core-app/src/CoreSettingsService.ts | 2 +- .../core-app/src/NavigationTree/NavigationTreeService.ts | 8 +------- .../core-app/src/shared/NodesManager/NavTreeResource.ts | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/webapp/packages/core-app/src/CoreSettingsService.ts b/webapp/packages/core-app/src/CoreSettingsService.ts index 6e3c90a1f0..4d5917a883 100644 --- a/webapp/packages/core-app/src/CoreSettingsService.ts +++ b/webapp/packages/core-app/src/CoreSettingsService.ts @@ -15,7 +15,7 @@ const defaultSettings = { 'app.logViewer.logBatchSize': 2000, 'app.logViewer.maxFailedRequests': 3, // temporary limit for all nodes children in app - 'app.childrenLimit': 500, + 'app.navigationTree.childrenLimit': 500, }; export type CoreSettings = typeof defaultSettings; diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts index 97722ab392..96cdfb80c5 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeService.ts @@ -72,13 +72,7 @@ export class NavigationTreeService { } getChildren(id: string): string[] | undefined { - const children = this.navTreeResource.get(id); - - if (children) { - return children.slice(0, this.navTreeResource.childrenLimit); - } - - return children; + return this.navTreeResource.get(id); } async navToNode(id: string, parentId: string): Promise { diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts index ad1115a6bb..fe7d329cf0 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavTreeResource.ts @@ -41,7 +41,7 @@ export class NavTreeResource extends CachedMapResource { protected metadata: MetadataMap; get childrenLimit(): number { - return this.coreSettingsService.settings.getValue('app.childrenLimit'); + return this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit'); } constructor( From 511bfcf7748b1b2f65393dcd58911926218cbdd2 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 2 Apr 2021 12:12:56 +0300 Subject: [PATCH 74/75] fix(core-app): CB-867 code optimization --- .../ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx index 5c506ba491..bc9e6c9c1e 100644 --- a/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx +++ b/webapp/packages/core-app/src/shared/ToolsPanel/LogViewTab/LogEntry/LogEntry.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import styled, { css, use } from 'reshadow'; +import styled, { css } from 'reshadow'; import { Link } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; @@ -31,9 +31,9 @@ const style = css` overflow: hidden; padding-right: 16px; text-overflow: ellipsis; - &[|details] { - cursor: pointer; - } + } + Link:hover { + cursor: pointer; } tr:hover Icon { @@ -43,7 +43,6 @@ const style = css` export const LogEntry = observer(function LogEntry({ item }: LogEntryProps) { const controller = useController(LogEntryController, item); - const isDetails = !!item.stackTrace; return styled(useStyles(style))( @@ -51,8 +50,8 @@ export const LogEntry = observer(function LogEntry({ item }: LogEntryProps) { {item.time} - - {isDetails ? {item.message} : item.message} + + {item.stackTrace ? {item.message} : item.message} From 9f04fa163bdf210e42bc6bf61a4a1ba603225f1c Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 2 Apr 2021 13:25:28 +0300 Subject: [PATCH 75/75] fix(plugin-gis-viewer): CB-904 add support for gis geometry collection --- .../configs/webpack.product.config.js | 2 +- .../packages/plugin-gis-viewer/package.json | 7 ++- .../src/GISValuePresentation.tsx | 11 ++-- .../src/terraformer-wkt.d.ts | 15 ----- webapp/yarn.lock | 55 ++++++++++++++++--- 5 files changed, 58 insertions(+), 32 deletions(-) delete mode 100644 webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts diff --git a/webapp/packages/core-cli/configs/webpack.product.config.js b/webapp/packages/core-cli/configs/webpack.product.config.js index 05f2621a36..62d61f4a8d 100644 --- a/webapp/packages/core-cli/configs/webpack.product.config.js +++ b/webapp/packages/core-cli/configs/webpack.product.config.js @@ -31,7 +31,7 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { cacheGroups: { vendor: { // TODO: we need another way to detect libraries to exclude - test: /[\\/]node_modules[\\/](?!(leaflet|react-leaflet|@terraformer|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, + test: /[\\/]node_modules[\\/](?!(leaflet|react-leaflet|wellknown|react-data-grid))(.[a-zA-Z0-9.\-_]+)[\\/]/, name: 'vendors', chunks: 'all', }, diff --git a/webapp/packages/plugin-gis-viewer/package.json b/webapp/packages/plugin-gis-viewer/package.json index 86c811d289..7e3b684632 100644 --- a/webapp/packages/plugin-gis-viewer/package.json +++ b/webapp/packages/plugin-gis-viewer/package.json @@ -38,11 +38,12 @@ "rxjs": "^6.x.x" }, "dependencies": { - "@terraformer/wkt": "^2.0.7", "leaflet": "^1.7.1", - "react-leaflet": "^3.1.0" + "react-leaflet": "^3.1.0", + "wellknown": "^0.5.0" }, "devDependencies": { - "@types/react-leaflet": "^2.8.1" + "@types/react-leaflet": "^2.8.1", + "@types/wellknown": "^0.5.1" } } diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index bd81676338..396fdc104c 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -6,16 +6,13 @@ * you may not use this file except in compliance with the License. */ -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// - import { observer } from 'mobx-react-lite'; import { useCallback, useMemo } from 'react'; +import wellknown from 'wellknown'; import { TextPlaceholder } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer'; -import { wktToGeoJSON } from '@terraformer/wkt'; import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap'; import { ResultSetGISAction } from './ResultSetGISAction'; @@ -53,7 +50,11 @@ export const GISValuePresentation: React.FC = observer(function GISValueP } try { - const parsedCellValue = wktToGeoJSON(cellValue.mapText || cellValue.text); + const parsedCellValue = wellknown.parse(cellValue.mapText || cellValue.text); + if (!parsedCellValue) { + continue; + } + result.push({ type: 'Feature', geometry: parsedCellValue, properties: { associatedCell: cell, srid: cellValue.srid } }); } catch (exception) { console.error(`Failed to parse "${cellValue.mapText || cellValue.text}" value.`); diff --git a/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts b/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts deleted file mode 100644 index 6aee3c7dfd..0000000000 --- a/webapp/packages/plugin-gis-viewer/src/terraformer-wkt.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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. - */ - -declare module '@terraformer/wkt' { - import * as terraformer from '@terraformer/wkt'; - - export function wktToGeoJSON(wkt: string): GeoJSON.GeometryObject; - export function GeoJSONToWkt(geoJSON: GeoJSON.GeometryObject): string; - -} diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 1f2c321629..2261a998c1 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -2712,11 +2712,6 @@ dependencies: defer-to-connect "^1.0.1" -"@terraformer/wkt@^2.0.7": - version "2.0.7" - resolved "https://registry.yarnpkg.com/@terraformer/wkt/-/wkt-2.0.7.tgz#970e7f72f361194fee4daca7738b6f15421d29d3" - integrity sha512-AOQHnQ5eqs5xWtIiX+ys/Xg80aYouTtBHiQDxRWFUhlmOBzrg7TfjrpC9gNPx8NP7XvuTzSwkARwCWCjOtDiWA== - "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -2912,6 +2907,11 @@ dependencies: "@types/node" "*" +"@types/wellknown@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@types/wellknown/-/wellknown-0.5.1.tgz#4fdf9a9a21e78b640e7c60b7b6857165153d6a9d" + integrity sha512-Q654r/2cjmh4XdFcZbRUKrY9Z3KmJJhXGQ4snwjWJCNrDNsNCJRaKQZRd/KPJaNGC0gZUxcmAu7184JpDX2Sjw== + "@typescript-eslint/eslint-plugin@^4.13.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -4553,6 +4553,15 @@ concat-stream@^2.0.0: readable-stream "^3.0.2" typedarray "^0.0.6" +concat-stream@~1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" + integrity sha1-cIl4Yk2FavQaWnQd790mHadSwmY= + dependencies: + inherits "~2.0.1" + readable-stream "~2.0.0" + typedarray "~0.0.5" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -7492,7 +7501,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -8970,7 +8979,7 @@ minimist-options@^3.0.1: arrify "^1.0.1" is-plain-obj "^1.1.0" -minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -10922,6 +10931,11 @@ pretty-format@^3.8.0: resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U= +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -11320,6 +11334,18 @@ read@1, read@~1.0.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@~2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + readdir-scoped-modules@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -12559,6 +12585,11 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -13130,7 +13161,7 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typedarray@^0.0.6: +typedarray@^0.0.6, typedarray@~0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= @@ -13636,6 +13667,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +wellknown@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/wellknown/-/wellknown-0.5.0.tgz#09ae9871fa826cf0a6ec1537ef00c379d78d7101" + integrity sha1-Ca6YcfqCbPCm7BU37wDDedeNcQE= + dependencies: + concat-stream "~1.5.0" + minimist "~1.2.0" + whatwg-fetch@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"