From 28ea689652eb66338823e514c27a9fdb9fc3ba4c Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Wed, 9 Sep 2020 11:51:08 +0300 Subject: [PATCH 01/18] CB-253 Check connection permissions --- .../schema/service.core.graphqls | 3 + .../cloudbeaver/model/session/WebSession.java | 89 ++++++++++++++----- .../service/WebServiceBindingBase.java | 8 +- .../service/core/DBWServiceCore.java | 3 + .../service/core/WebServiceBindingCore.java | 1 + .../service/core/impl/WebServiceCore.java | 23 +++-- 6 files changed, 93 insertions(+), 34 deletions(-) diff --git a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls index 039ebb7ff2..d79a6d4dd7 100644 --- a/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/service.core.graphqls @@ -303,6 +303,9 @@ extend type Mutation { # Refreshes session on server and returns its state touchSession: Boolean + # Refresh session connection list + refreshSessionConnections: Boolean + # Refreshes session on server and returns its state changeSessionLanguage(locale: String): Boolean 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 014b37311d..0f6a7560a4 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 @@ -16,6 +16,8 @@ */ package io.cloudbeaver.model.session; +import io.cloudbeaver.DBWConnectionGrant; +import io.cloudbeaver.DBWConstants; import io.cloudbeaver.DBWSecurityController; import io.cloudbeaver.DBWebException; import io.cloudbeaver.model.WebAsyncTaskInfo; @@ -52,6 +54,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.stream.Collectors; /** * Web session. @@ -153,7 +156,11 @@ public class WebSession implements DBASession { return user; } - public synchronized Set getSessionPermissions() throws DBCException { + public synchronized boolean hasPermission(String perm) { + return getSessionPermissions().contains(perm); + } + + public synchronized Set getSessionPermissions() { if (sessionPermissions == null) { refreshSessionAuth(); } @@ -172,11 +179,7 @@ public class WebSession implements DBASession { } catch (DBCException e) { log.error(e); } - try { - refreshSessionAuth(); - } catch (DBCException e) { - log.error(e); - } + refreshSessionAuth(); } private void initNavigatorModel() { @@ -189,17 +192,59 @@ public class WebSession implements DBASession { this.databases = projectNode.getDatabases(); this.locale = Locale.getDefault().getLanguage(); + try { + this.refreshConnections(); + } catch (Exception e) { + log.error("Error getting connection list", e); + } + } + + public void refreshConnections() { + + // Add all provided datasources to the session + List connList = new ArrayList<>(); + for (DBPDataSourceContainer ds : databases.getDataSourceRegistry().getDataSources()) { + if (ds.isProvided()) { + WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds); + connList.add(connectionInfo); + } + } + filterAccessibleConnections(connList); + // Add all provided datasources to the session synchronized (connections) { - for (DBPDataSourceContainer ds : databases.getDataSourceRegistry().getDataSources()) { - if (ds.isProvided()) { - WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds); - connections.put(connectionInfo.getId(), connectionInfo); - } + connections.clear(); + for (WebConnectionInfo connectionInfo : connList) { + connections.put(connectionInfo.getId(), connectionInfo); } } } + public void filterAccessibleConnections(List connections) { + if (this.hasPermission(DBWConstants.PERMISSION_ADMIN)) { + // All connections are accessible + return; + } + Set allowedConnections = getAccessibleConnectionIds(); + connections.removeIf(c -> !allowedConnections.contains(c.getId())); + } + + @NotNull + public Set getAccessibleConnectionIds() { + CBApplication application = CBApplication.getInstance(); + String subjectId = user == null ? + application.getAppConfiguration().getAnonymousUserRole() : user.getUserId(); + + try { + return Arrays.stream(application.getSecurityController() + .getSubjectConnectionAccess(new String[]{subjectId})) + .map(DBWConnectionGrant::getConnectionId).collect(Collectors.toSet()); + } catch (DBCException e) { + log.error("Error reading connection grants", e); + return Collections.emptySet(); + } + } + private void resetSessionCache() throws DBCException { // Clear attributes synchronized (attributes) { @@ -230,17 +275,21 @@ public class WebSession implements DBASession { this.databases = null; } - private void refreshSessionAuth() throws DBCException { - CBApplication application = CBPlatform.getInstance().getApplication(); - if (this.user == null) { - if (application.getAppConfiguration().isAnonymousAccessEnabled()) { - sessionPermissions = application.getSecurityController().getSubjectPermissions( - application.getAppConfiguration().getAnonymousUserRole()); + private void refreshSessionAuth() { + try { + CBApplication application = CBPlatform.getInstance().getApplication(); + if (this.user == null) { + if (application.getAppConfiguration().isAnonymousAccessEnabled()) { + sessionPermissions = application.getSecurityController().getSubjectPermissions( + application.getAppConfiguration().getAnonymousUserRole()); + } else { + sessionPermissions = Collections.emptySet(); + } } else { - sessionPermissions = Collections.emptySet(); + sessionPermissions = application.getSecurityController().getUserPermissions(this.user.getUserId()); } - } else { - sessionPermissions = application.getSecurityController().getUserPermissions(this.user.getUserId()); + } catch (Exception e) { + log.error("Error reading session permissions", e); } } 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 19a262b0c1..1affc3045f 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 @@ -28,7 +28,6 @@ import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.server.CBPlatform; import io.cloudbeaver.server.graphql.GraphQLEndpoint; -import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.utils.CommonUtils; import javax.servlet.http.HttpServletRequest; @@ -147,12 +146,7 @@ public abstract class WebServiceBindingBase impleme } if (!CBApplication.getInstance().isConfigurationMode()) { // Check permissions - Set sessionPermissions; - try { - sessionPermissions = session.getSessionPermissions(); - } catch (DBCException e) { - throw new DBWebExceptionAccessDenied("Can't retrieve session permissions", e); - } + Set sessionPermissions = session.getSessionPermissions(); if (CommonUtils.isEmpty(sessionPermissions)) { throw new DBWebExceptionAccessDenied("Anonymous access restricted"); } 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 f19acfc11c..c399bcd87b 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 @@ -69,6 +69,9 @@ public interface DBWServiceCore extends DBWService { @WebAction(requirePermissions = {}) boolean touchSession(HttpServletRequest request) throws DBWebException; + @WebAction(requirePermissions = {}) + boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException; + @WebAction boolean changeSessionLanguage(WebSession webSession, String locale) throws DBWebException; 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 87f3f370cd..8a42268630 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 @@ -66,6 +66,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase .dataFetcher("openSession", env -> getService(env).openSession(sessionManager.getWebSession(GraphQLEndpoint.getServletRequest(env), false))) .dataFetcher("closeSession", env -> getService(env).closeSession(GraphQLEndpoint.getServletRequest(env))) .dataFetcher("touchSession", env -> getService(env).touchSession(GraphQLEndpoint.getServletRequest(env))) + .dataFetcher("refreshSessionConnections", env -> getService(env).refreshSessionConnections(GraphQLEndpoint.getServletRequest(env))) .dataFetcher("changeSessionLanguage", env -> getService(env).changeSessionLanguage(getWebSession(env), env.getArgument("locale"))) .dataFetcher("openConnection", env -> getService(env).openConnection(getWebSession(env), getConnectionConfig(env))) 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 79092f8208..767edbc914 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 @@ -33,7 +33,6 @@ import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.connection.DBPDriver; import org.jkiss.dbeaver.model.data.json.JSONUtils; -import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNative; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.registry.DataSourceDescriptor; @@ -101,10 +100,13 @@ public class WebServiceCore implements DBWServiceCore { public List getTemplateConnections(WebSession webSession) throws DBWebException { List result = new ArrayList<>(); for (DBPDataSourceContainer ds : WebServiceUtils.getDataSourceRegistry().getDataSources()) { - if (ds.isTemplate() && CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver())) { + if (ds.isTemplate() && + CBPlatform.getInstance().getApplicableDrivers().contains(ds.getDriver())) + { result.add(new WebConnectionInfo(webSession, ds)); } } + webSession.filterAccessibleConnections(result); return result; } @@ -117,11 +119,7 @@ public class WebServiceCore implements DBWServiceCore { DBWConstants.PERMISSION_ADMIN }; } - try { - return webSession.getSessionPermissions().toArray(new String[0]); - } catch (DBCException e) { - throw new DBWebException("Error reading session permissions", e); - } + return webSession.getSessionPermissions().toArray(new String[0]); } @Override @@ -149,6 +147,17 @@ public class WebServiceCore implements DBWServiceCore { return CBPlatform.getInstance().getSessionManager().touchSession(request); } + @Override + public boolean refreshSessionConnections(HttpServletRequest request) throws DBWebException { + WebSession session = CBPlatform.getInstance().getSessionManager().getWebSession(request); + if (session == null) { + return false; + } else { + session.refreshConnections(); + return true; + } + } + @Override public boolean changeSessionLanguage(WebSession webSession, String locale) { webSession.setLocale(locale); From 411b34647cfc50b616b0ae7bd0d246ddf94d5dfe Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 12:59:16 +0300 Subject: [PATCH 02/18] chore: misc improvements --- .../src/Administration/ItemContent.tsx | 2 +- .../AdministrationItem/IAdministrationItem.ts | 2 +- .../ConnectionsAdministrationController.ts | 19 +--- .../ConnectionsAdministrationService.ts | 5 +- .../CreateConnection/CreateConnection.tsx | 4 +- .../CreateConnectionController.tsx | 19 +--- .../CreateConnection/DriverList.tsx | 4 +- .../Connections/DatabasesSearch.tsx | 89 ------------------- .../core-connections/src/locales/en.ts | 2 + .../core-connections/src/locales/ru.ts | 4 +- .../Users/UsersAdministrationService.ts | 1 - .../DriverSelectorDialog/DriverSelector.tsx | 4 +- 12 files changed, 21 insertions(+), 134 deletions(-) delete mode 100644 webapp/packages/core-connections/src/Administration/Connections/DatabasesSearch.tsx diff --git a/webapp/packages/core-administration/src/Administration/ItemContent.tsx b/webapp/packages/core-administration/src/Administration/ItemContent.tsx index 3b47d6e722..18346d6e77 100644 --- a/webapp/packages/core-administration/src/Administration/ItemContent.tsx +++ b/webapp/packages/core-administration/src/Administration/ItemContent.tsx @@ -40,7 +40,7 @@ export const ItemContent = observer(function ItemContent({ if (activeItemSub) { const sub = administrationItemService.getItemSub(item, activeItemSub); if (sub) { - const Component = sub.getComponent(); + const Component = sub.getComponent ? sub.getComponent() : item.getContentComponent(); return ; } diff --git a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts index 228f4971ec..98313809a4 100644 --- a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts +++ b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts @@ -49,7 +49,7 @@ export type AdministrationItemSubCanActivateEvent = ( export interface IAdministrationItemSubItem { name: string; - getComponent(): AdministrationItemSubContentComponent; + getComponent?: () => AdministrationItemSubContentComponent; onActivate?: AdministrationItemSubEvent; canActivate?: AdministrationItemSubCanActivateEvent; } diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationController.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationController.ts index d9e03c5135..3f5e005db9 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationController.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationController.ts @@ -11,8 +11,7 @@ import { observable, computed } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk'; +import { resourceKeyList } from '@cloudbeaver/core-sdk'; import { ConnectionsResource, isSearchedConnection } from '../ConnectionsResource'; import { ConnectionsAdministrationNavService } from './ConnectionsAdministrationNavService'; @@ -22,8 +21,6 @@ export class ConnectionsAdministrationController { @observable isProcessing = false; readonly selectedItems = observable(new Map()) readonly expandedItems = observable(new Map()) - readonly error = new GQLErrorCatcher(); - @computed get connections() { return Array.from(this.connectionsResource.data.values()) @@ -65,9 +62,7 @@ export class ConnectionsAdministrationController { try { await this.connectionsResource.refresh('all'); } catch (exception) { - if (!this.error.catch(exception)) { - this.notificationService.logException(exception, 'Connections update failed'); - } + this.notificationService.logException(exception, 'Connections update failed'); } } @@ -108,17 +103,9 @@ export class ConnectionsAdministrationController { this.expandedItems.delete(id); } } catch (exception) { - if (!this.error.catch(exception)) { - this.notificationService.logException(exception, 'Connections delete failed'); - } + this.notificationService.logException(exception, 'Connections delete failed'); } finally { this.isProcessing = false; } } - - showDetails = () => { - if (this.error.exception) { - this.commonDialogService.open(ErrorDetailsDialog, this.error.exception); - } - } } diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts index 2cdf222e7d..110b90b1f4 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts @@ -36,10 +36,7 @@ export class ConnectionsAdministrationService extends Bootstrap { description: 'connections_administration_configuration_wizard_step_description', }, sub: [ - { - name: 'create', - getComponent: () => ConnectionsAdministration, - }, + { name: 'create' }, ], getContentComponent: () => ConnectionsAdministration, getDrawerComponent: () => ConnectionsDrawerItem, diff --git a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnection.tsx b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnection.tsx index fc5ec32189..0470ff1752 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnection.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnection.tsx @@ -202,10 +202,10 @@ export const CreateConnection = observer(function CreateConnection({ - {translate('Driver')} + {translate('connections_connection_driver')} - {translate('Search Database')} + {translate('connections_connection_create_search_database')} diff --git a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnectionController.tsx b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnectionController.tsx index 0675851835..40be90b9f0 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnectionController.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/CreateConnectionController.tsx @@ -9,15 +9,12 @@ import { observable, computed } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; -import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLErrorCatcher, AdminConnectionSearchInfo, ConnectionInfo } from '@cloudbeaver/core-sdk'; +import { AdminConnectionSearchInfo, ConnectionInfo } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; import { DBDriverResource } from '../../../DBDriverResource'; import { ConnectionsResource } from '../../ConnectionsResource'; -import { ConnectionsAdministrationNavService } from '../ConnectionsAdministrationNavService'; @injectable() export class CreateConnectionController { @@ -37,14 +34,10 @@ export class CreateConnectionController { return this.dbDriverResource.get(this.connection.driverId); } - readonly error = new GQLErrorCatcher(); - constructor( private notificationService: NotificationService, private connectionsResource: ConnectionsResource, - private commonDialogService: CommonDialogService, private dbDriverResource: DBDriverResource, - private connectionsAdministrationNavService: ConnectionsAdministrationNavService ) { this.credentials = {}; this.databases = []; @@ -67,9 +60,7 @@ export class CreateConnectionController { this.databases = await this.connectionsResource.searchDatabases(hosts); } catch (exception) { - if (!this.error.catch(exception)) { - this.notificationService.logException(exception, 'Databases search failed'); - } + this.notificationService.logException(exception, 'Databases search failed'); } finally { this.isProcessing = false; } @@ -109,10 +100,4 @@ export class CreateConnectionController { this.connection = null; this.availableDrivers = []; } - - showDetails = () => { - if (this.error.exception) { - this.commonDialogService.open(ErrorDetailsDialog, this.error.exception); - } - } } diff --git a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/DriverList.tsx b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/DriverList.tsx index fb9b36aca5..1dfad0a024 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/DriverList.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/CreateConnection/DriverList.tsx @@ -10,6 +10,7 @@ import { observer } from 'mobx-react'; import { useState, useMemo } from 'react'; import { ItemListSearch, ItemList } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; import { DBDriver } from '../../../DBDriverResource'; import { Driver } from './Driver'; @@ -21,6 +22,7 @@ type Props = { } export const DriverList = observer(function DriverList({ drivers, className, onSelect }: Props) { + const translate = useTranslate(); const [search, setSearch] = useState(''); const filteredDrivers = useMemo(() => { if (!search) { @@ -31,7 +33,7 @@ export const DriverList = observer(function DriverList({ drivers, className, onS return ( - + {filteredDrivers.map(driver => )} ); diff --git a/webapp/packages/core-connections/src/Administration/Connections/DatabasesSearch.tsx b/webapp/packages/core-connections/src/Administration/Connections/DatabasesSearch.tsx deleted file mode 100644 index d7b2791e8e..0000000000 --- a/webapp/packages/core-connections/src/Administration/Connections/DatabasesSearch.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/* - * cloudbeaver - Cloud Database Manager - * Copyright (C) 2020 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { observer } from 'mobx-react'; -import styled, { css } from 'reshadow'; - -import { Button, InputField, SubmittingForm } from '@cloudbeaver/core-blocks'; -import { useTranslate } from '@cloudbeaver/core-localization'; -import { useStyles, composes } from '@cloudbeaver/core-theming'; - -const styles = composes( - css` - SubmittingForm { - composes: theme-border-color-background from global; - } - `, - css` - SubmittingForm { - flex: 1; - display: flex; - flex-wrap: wrap; - align-items: center; - border-top: solid 1px; - } - - group { - box-sizing: border-box; - display: flex; - } - - action { - padding-left: 24px; - } - - InputField { - width: 450px; - } - ` -); - -type Props = { - hosts: string; - className?: string; - onChange(hosts: string): void; - onSearch(): void; - disabled?: boolean; -} - -export const DatabasesSearch = observer(function DatabasesSearch({ - hosts, - className, - onChange, - onSearch, - disabled, -}: Props) { - const translate = useTranslate(); - - return styled(useStyles(styles))( - - - - {translate('connections_connection_edit_search_hosts')} - - - - - - - ); -}); diff --git a/webapp/packages/core-connections/src/locales/en.ts b/webapp/packages/core-connections/src/locales/en.ts index 62b1f171a5..e6a3e0f8b0 100644 --- a/webapp/packages/core-connections/src/locales/en.ts +++ b/webapp/packages/core-connections/src/locales/en.ts @@ -5,6 +5,7 @@ export default [ ['connections_administration_new_connection', 'New connection'], ['connections_administration_configuration_wizard_step_title', 'Database connections'], ['connections_administration_configuration_wizard_step_description', 'Database connections'], + ['connections_connection_create_search_database', 'Search Database'], ['connections_connection_edit_authentication', 'Authentication'], ['connections_connection_edit_access', 'Access'], ['connections_connection_edit_access_load_failed', 'Connection access loading failed'], @@ -19,4 +20,5 @@ export default [ ['connections_connection_port', 'Port'], ['connections_connection_template', 'Template'], ['connections_connection_test', 'Test Connection'], + ['connections_driver_search_placeholder', 'Type driver name...'], ]; diff --git a/webapp/packages/core-connections/src/locales/ru.ts b/webapp/packages/core-connections/src/locales/ru.ts index e479556b49..dcf5c3b659 100644 --- a/webapp/packages/core-connections/src/locales/ru.ts +++ b/webapp/packages/core-connections/src/locales/ru.ts @@ -5,6 +5,7 @@ export default [ ['connections_administration_new_connection', 'Создание подключения'], ['connections_administration_configuration_wizard_step_title', 'Подключения к базам'], ['connections_administration_configuration_wizard_step_description', 'Подключения к базам'], + ['connections_connection_create_search_database', 'Поиск баз'], ['connections_connection_edit_authentication', 'Авторизация'], ['connections_connection_edit_access', 'Доступ'], ['connections_connection_edit_access_load_failed', 'Не удалось загрузить информацию доступа'], @@ -14,9 +15,10 @@ export default [ ['connections_connection_address', 'Адрес'], ['connections_connection_name', 'Название'], ['connections_connection_description', 'Описапние'], - ['connections_connection_driver', 'Driver'], + ['connections_connection_driver', 'Драйвер'], ['connections_connection_host', 'Хост'], ['connections_connection_port', 'Порт'], ['connections_connection_template', 'Шаблон'], ['connections_connection_test', 'Проверить подключение'], + ['connections_driver_search_placeholder', 'Введите название драйвера...'], ]; diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationService.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationService.ts index de2cec3da0..9078aaa62c 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationService.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationService.ts @@ -32,7 +32,6 @@ export class UsersAdministrationService extends Bootstrap { sub: [ { name: UsersAdministrationNavigationService.AddItemName, - getComponent: () => UsersAdministration, onActivate: this.onCreateActivate.bind(this), }, ], diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/DriverSelectorDialog/DriverSelector.tsx b/webapp/packages/plugin-connection-custom/src/CustomConnection/DriverSelectorDialog/DriverSelector.tsx index e0a21f4a0d..ef5938490c 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/DriverSelectorDialog/DriverSelector.tsx +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/DriverSelectorDialog/DriverSelector.tsx @@ -10,6 +10,7 @@ import { observer } from 'mobx-react'; import { useState, useMemo } from 'react'; import { ItemListSearch, ItemList } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; import { Driver, IDriver } from './Driver'; @@ -20,6 +21,7 @@ type DriverSelectorProps = { } export const DriverSelector = observer(function DriverSelector({ drivers, className, onSelect }: DriverSelectorProps) { + const translate = useTranslate(); const [search, setSearch] = useState(''); const filteredDrivers = useMemo(() => { if (!search) { @@ -30,7 +32,7 @@ export const DriverSelector = observer(function DriverSelector({ drivers, classN return ( - + {filteredDrivers.map(driver => )} ); From 78b821c5c14ca3f0c666a1373e06ce9aea679e8e Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 13:01:12 +0300 Subject: [PATCH 03/18] feat(core-connections): synchronisation --- .../AdministrationScreenService.ts | 5 ++++- .../AdministrationScreenServiceBootstrap.ts | 6 ++++++ .../ConnectionForm/Options/OptionsController.ts | 2 +- .../ConnectionsAdministrationService.ts | 17 ++++++++++++++++- .../src/Administration/ConnectionsResource.ts | 3 +++ .../src/ConnectionInfoResource.ts | 10 +++++++++- .../src/LocalizationService.ts | 12 +++++++----- .../core-localization/src/locales/ru.ts | 8 ++++---- .../packages/core-sdk/src/CachedMapResource.ts | 16 ++++++++++++++-- .../connections/refreshSessionConnections.gql | 3 +++ webapp/packages/core-sdk/src/sdk.ts | 13 +++++++++++++ .../packages/core-theming/src/styles/_tabs.scss | 2 ++ .../ConnectionFormDialogController.ts | 12 ++++++------ 13 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 webapp/packages/core-sdk/src/queries/connections/refreshSessionConnections.gql diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index 06533dbc53..ed98975f4a 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -9,6 +9,7 @@ import { computed, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; +import { IExecutor, Executor } from '@cloudbeaver/core-executor'; import { ServerConfigResource } from '@cloudbeaver/core-root'; import { ScreenService, RouterService } from '@cloudbeaver/core-routing'; import { LocalStorageSaveService } from '@cloudbeaver/core-settings'; @@ -57,6 +58,7 @@ export class AdministrationScreenService { get isConfigurationMode() { return !!this.serverConfigResource.data?.configurationMode; } + readonly activationEvent: IExecutor; constructor( private screenService: ScreenService, @@ -66,6 +68,7 @@ export class AdministrationScreenService { private serverConfigResource: ServerConfigResource ) { this.itemState = new Map(); + this.activationEvent = new Executor(); this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE); } @@ -124,7 +127,7 @@ export class AdministrationScreenService { this.itemState.clear(); } - private isAdministrationRouteActive() { + isAdministrationRouteActive() { return this.screenService.isActive(AdministrationScreenService.screenName) || this.screenService.isActive(AdministrationScreenService.setupName); } diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenServiceBootstrap.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenServiceBootstrap.ts index b93f5d3477..71d4029999 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenServiceBootstrap.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenServiceBootstrap.ts @@ -57,6 +57,7 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap { ], component: AdministrationScreen, onActivate: this.handleActivate.bind(this), + onDeactivate: this.handleDeactivate.bind(this), }); this.screenService.create({ @@ -99,6 +100,10 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap { } private async handleDeactivate() { + if (!this.administrationScreenService.isAdministrationRouteActive()) { + this.administrationScreenService.activationEvent.execute(false); + } + if (this.administrationScreenService.isConfigurationMode && !this.screenService.isActive(AdministrationScreenService.setupName)) { this.administrationScreenService.navigateToRoot(); @@ -122,6 +127,7 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap { if (!await this.checkPermissions()) { return; } + this.administrationScreenService.activationEvent.execute(true); if (this.administrationScreenService.activeItem) { await this.administrationItemService.activate( diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts index 69e135f8d2..e245f847bc 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/Options/OptionsController.ts @@ -100,7 +100,7 @@ implements IInitializableController { const prevDriver = this.dbDriverResource.get(prevDriverId || ''); if (this.model.connection.host === prevDriver?.defaultServer) { - this.model.connection.host = this.driver?.defaultServer; + this.model.connection.host = this.driver?.defaultServer || 'localhost'; } if (this.model.connection.port === prevDriver?.defaultPort) { diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts index 110b90b1f4..b4ba01bfd9 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts @@ -6,10 +6,12 @@ * you may not use this file except in compliance with the License. */ -import { AdministrationItemService, AdministrationItemType } from '@cloudbeaver/core-administration'; +import { AdministrationItemService, AdministrationItemType, AdministrationScreenService } from '@cloudbeaver/core-administration'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; +import { GraphQLService } from '@cloudbeaver/core-sdk'; +import { ConnectionInfoResource } from '../../ConnectionInfoResource'; import { DBDriverResource } from '../../DBDriverResource'; import { ConnectionsResource } from '../ConnectionsResource'; import { ConnectionsAdministration } from './ConnectionsAdministration'; @@ -18,15 +20,20 @@ import { ConnectionsDrawerItem } from './ConnectionsDrawerItem'; @injectable() export class ConnectionsAdministrationService extends Bootstrap { constructor( + private administrationScreenService: AdministrationScreenService, private administrationItemService: AdministrationItemService, private notificationService: NotificationService, private connectionsResource: ConnectionsResource, + private connectionInfoResource: ConnectionInfoResource, private dbDriverResource: DBDriverResource, + private graphQLService: GraphQLService ) { super(); } register() { + this.administrationScreenService.activationEvent.addHandler((_, state) => this.handleDeactivate(state)); + this.administrationItemService.create({ name: 'connections', type: AdministrationItemType.Default, @@ -46,6 +53,14 @@ export class ConnectionsAdministrationService extends Bootstrap { load(): void | Promise { } + private async handleDeactivate(state: boolean) { + // if (state) { + // return; + // } + + // this.connectionInfoResource.markOutdated(); + } + private async loadConnections() { try { await this.connectionsResource.loadAll(); diff --git a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts index c1ff34be44..44b9e16631 100644 --- a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts +++ b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts @@ -87,6 +87,7 @@ export class ConnectionsResource extends CachedMapResource this.updateConnection(id, config)); + await this.graphQLService.gql.refreshSessionConnections(); return this.get(id)!; } async delete(key: ResourceKey) { await this.performUpdate(key, () => this.deleteConnectionTask(key)); + await this.graphQLService.gql.refreshSessionConnections(); } async loadAccessSubjects(connectionId: string): Promise { diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 7994b1d86d..e3c48bf066 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -14,12 +14,20 @@ import { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; +import { ConnectionsResource } from './Administration/ConnectionsResource'; + export type Connection = Pick & { authProperties?: ObjectPropertyInfo[] } @injectable() export class ConnectionInfoResource extends CachedMapResource { - constructor(private graphQLService: GraphQLService) { + constructor( + private graphQLService: GraphQLService, + connectionsResource: ConnectionsResource + ) { super(new Map()); + connectionsResource.onItemAdd.subscribe(this.load.bind(this)); + connectionsResource.onItemDelete.subscribe(this.delete.bind(this)); + connectionsResource.onDataOutdated.subscribe(this.markOutdated.bind(this)); } async createFromTemplate(templateId: string): Promise { diff --git a/webapp/packages/core-localization/src/LocalizationService.ts b/webapp/packages/core-localization/src/LocalizationService.ts index 9f27570e09..2227519dcd 100644 --- a/webapp/packages/core-localization/src/LocalizationService.ts +++ b/webapp/packages/core-localization/src/LocalizationService.ts @@ -51,14 +51,16 @@ export class LocalizationService extends Bootstrap { } readonly translate = (token: TLocalizationToken): string => { - let locale = this.localeMap.get(this.getCurrentLanguage()); + let translation = this.localeMap + .get(this.getCurrentLanguage()) + ?.get(token); - if (!locale) { - locale = this.localeMap.get(DEFAULT_LOCALE_NAME); + if (!translation) { + translation = this.localeMap + .get(DEFAULT_LOCALE_NAME) + ?.get(token); } - const translation = locale && locale.get(token); - if (typeof translation === 'string') { return translation; } diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 2e4eb06d5d..5749d96f65 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -13,16 +13,16 @@ export const defaultRULocale = [ ['ui_errors_details', 'Информация'], ['ui_search', 'Поиск...'], ['ui_data_saving_error', 'Ошибка при сохранении изменений'], - ['block_properties_name', 'Название'], - ['block_properties_value', 'Значение'], - ['block_properties_add', 'Добавить'], + ['block_properties_table_name', 'Название'], + ['block_properties_table_value', 'Значение'], + ['block_properties_table_add', 'Добавить'], ['root_permission_denied', 'Отказано в доступе'], ['app_root_session_expired_title', 'Сесстия истекла'], ['app_root_session_expired_message', 'Сессия истекла. Хотите перезагрузить страницу?'], ['app_root_session_expired_reload', 'Перезагрузить'], ['app_shared_connectionMenu_newConnection', 'Новое подключение'], ['app_shared_connectionMenu_connection', 'Подключение'], - ['app_shared_connectionMenu_custom', 'Custom'], + ['app_shared_connectionMenu_custom', 'Настроить'], ['app_shared_connectionMenu_disconnect', 'Отключить'], ['app_shared_toolsMenu_tools', 'Инструменты'], ['app_shared_toolsMenu_logViewer', 'Просмотр лога'], diff --git a/webapp/packages/core-sdk/src/CachedMapResource.ts b/webapp/packages/core-sdk/src/CachedMapResource.ts index d1ec20bf5e..6730a491b8 100644 --- a/webapp/packages/core-sdk/src/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/CachedMapResource.ts @@ -48,7 +48,13 @@ export abstract class CachedMapResource extends CachedResource< return this.outdated.has(key); } - markOutdated(key: ResourceKey): void { + markOutdated(): void + markOutdated(key: ResourceKey): void + markOutdated(key?: ResourceKey): void { + if (!key) { + key = resourceKeyList(Array.from(this.data.keys())); + } + if (isResourceKeyList(key)) { for (const itemKey of key.list) { this.outdated.add(itemKey); @@ -59,7 +65,13 @@ export abstract class CachedMapResource extends CachedResource< this.outdatedSubject.next(key); } - markUpdated(key: ResourceKey): void { + markUpdated(): void + markUpdated(key: ResourceKey): void + markUpdated(key?: ResourceKey): void { + if (!key) { + key = resourceKeyList(Array.from(this.data.keys())); + } + if (isResourceKeyList(key)) { for (const itemKey of key.list) { this.outdated.delete(itemKey); diff --git a/webapp/packages/core-sdk/src/queries/connections/refreshSessionConnections.gql b/webapp/packages/core-sdk/src/queries/connections/refreshSessionConnections.gql new file mode 100644 index 0000000000..0113443602 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/refreshSessionConnections.gql @@ -0,0 +1,3 @@ +mutation refreshSessionConnections { + refreshSessionConnections +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index cf26f636c1..dd0ed7e8a5 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -252,6 +252,7 @@ export type Mutation = { openConnection: ConnectionInfo; openSession: SessionInfo; readDataFromContainer?: Maybe; + refreshSessionConnections?: Maybe; setConnectionNavigatorSettings: Scalars['Boolean']; sqlContextCreate: SqlContextInfo; sqlContextDestroy: Scalars['Boolean']; @@ -1023,6 +1024,10 @@ export type InitConnectionMutationVariables = Exact<{ export type InitConnectionMutation = { connection: Pick }; +export type RefreshSessionConnectionsMutationVariables = Exact<{ [key: string]: never }>; + +export type RefreshSessionConnectionsMutation = Pick; + export type GetTemplateConnectionsQueryVariables = Exact<{ [key: string]: never }>; export type GetTemplateConnectionsQuery = { connections: Array> }; @@ -1712,6 +1717,11 @@ export const InitConnectionDocument = ` } } `; +export const RefreshSessionConnectionsDocument = ` + mutation refreshSessionConnections { + refreshSessionConnections +} + `; export const GetTemplateConnectionsDocument = ` query getTemplateConnections { connections: templateConnections { @@ -2295,6 +2305,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = initConnection(variables: InitConnectionMutationVariables): Promise { return withWrapper(() => client.request(InitConnectionDocument, variables)); }, + refreshSessionConnections(variables?: RefreshSessionConnectionsMutationVariables): Promise { + return withWrapper(() => client.request(RefreshSessionConnectionsDocument, variables)); + }, getTemplateConnections(variables?: GetTemplateConnectionsQueryVariables): Promise { return withWrapper(() => client.request(GetTemplateConnectionsDocument, variables)); }, diff --git a/webapp/packages/core-theming/src/styles/_tabs.scss b/webapp/packages/core-theming/src/styles/_tabs.scss index 225d0346f6..e18f471fad 100644 --- a/webapp/packages/core-theming/src/styles/_tabs.scss +++ b/webapp/packages/core-theming/src/styles/_tabs.scss @@ -84,6 +84,8 @@ } tab-title { + overflow: hidden; + text-overflow: ellipsis; padding: 0 12px; } diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts index 33af008bcd..33aa7dbe83 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialogController.ts @@ -136,12 +136,12 @@ implements IInitializableController, IDestructibleController { @action private setDriverDefaults() { - this.config.name = `${this.driver?.name} (custom)`; - this.config.driverId = this.driver?.id; - this.config.host = ''; - this.config.port = this.driver?.defaultPort || ''; - this.config.databaseName = ''; - this.config.url = this.driver?.sampleURL || ''; + this.config.name = `${this.driver.name} (custom)`; + this.config.driverId = this.driver.id; + this.config.host = this.driver.defaultServer || 'localhost'; + this.config.port = this.driver.defaultPort || ''; + this.config.databaseName = this.driver.defaultDatabase; + this.config.url = this.driver.sampleURL || ''; this.config.properties = {}; this.config.authModelId = this.driver.defaultAuthModel; this.config.credentials = {}; From d4f8a06a6d158e11bf8db1266cf28ba116864655 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 13:14:30 +0300 Subject: [PATCH 04/18] fix(core-connections): filtering template connections --- .../src/ConnectionInfoResource.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index e3c48bf066..06f8e64965 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -11,7 +11,10 @@ import { ConnectionInfo, GraphQLService, CachedMapResource, - ObjectPropertyInfo + ObjectPropertyInfo, + ResourceKey, + isResourceKeyList, + resourceKeyList } from '@cloudbeaver/core-sdk'; import { ConnectionsResource } from './Administration/ConnectionsResource'; @@ -22,10 +25,10 @@ export type Connection = Pick { constructor( private graphQLService: GraphQLService, - connectionsResource: ConnectionsResource + private connectionsResource: ConnectionsResource ) { super(new Map()); - connectionsResource.onItemAdd.subscribe(this.load.bind(this)); + connectionsResource.onItemAdd.subscribe(this.addHandler.bind(this)); connectionsResource.onItemDelete.subscribe(this.delete.bind(this)); connectionsResource.onDataOutdated.subscribe(this.markOutdated.bind(this)); } @@ -87,6 +90,19 @@ export class ConnectionInfoResource extends CachedMapResource) { + if (isResourceKeyList(key)) { + this.load(resourceKeyList(key.list.filter(id => !this.connectionsResource.get(id)?.template))); + return; + } + + if (this.connectionsResource.get(key)?.template) { + return; + } + + this.load(key); + } + private async getAuthProperties(id: string): Promise { const { connection: { authProperties } } = await this.graphQLService.gql.connectionAuthProperties({ id }); From 167827158a5a3d9c576c1d8c5265622fd42d7610 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Wed, 9 Sep 2020 14:11:08 +0300 Subject: [PATCH 05/18] CB-253 Connections refresh --- .../src/io/cloudbeaver/model/session/WebSession.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 0f6a7560a4..caf215f32f 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 @@ -33,6 +33,7 @@ import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.access.DBASession; +import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.exec.DBCException; import org.jkiss.dbeaver.model.meta.Association; @@ -203,7 +204,10 @@ public class WebSession implements DBASession { // Add all provided datasources to the session List connList = new ArrayList<>(); - for (DBPDataSourceContainer ds : databases.getDataSourceRegistry().getDataSources()) { + DBPDataSourceRegistry registry = databases.getDataSourceRegistry(); + registry.refreshConfig(); + this.databases.refreshChildren(); + for (DBPDataSourceContainer ds : registry.getDataSources()) { if (ds.isProvided()) { WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds); connList.add(connectionInfo); From e5d4d91fb2b21b7535650be3f297f88af93ff1c6 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 14:41:49 +0300 Subject: [PATCH 06/18] fix(plugin-authentication): disable auth in configuration mode --- .../packages/plugin-authentication/src/AuthenticationService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index 8564fcd4b3..f476a1f5e4 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -35,7 +35,7 @@ export class AuthenticationService extends Bootstrap { throw new Error('Can\'t configure Authentication'); } - if (!config.authenticationEnabled) { + if (!config.authenticationEnabled || config.configurationMode) { return; } From bb9188952fdfe421894c33ed30e90ea76da5e249 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 14:55:36 +0300 Subject: [PATCH 07/18] fix(core-app): nav tree connections synchronisation --- .../src/shared/NodesManager/NavNodeManagerService.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index 6d3b946d7d..4360517e87 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -341,13 +341,10 @@ export class NavNodeManagerService extends Bootstrap { await this.refreshNode(nodeId); - if (connectionInfo?.features.includes(EConnectionFeature.temporary)) { + const tree = this.navTree.get(ROOT_NODE_PATH); - const tree = this.navTree.get(ROOT_NODE_PATH); - - if (!tree?.includes(nodeId)) { - this.navTree.unshiftToNode(ROOT_NODE_PATH, [nodeId]); - } + if (!tree?.includes(nodeId)) { + this.navTree.unshiftToNode(ROOT_NODE_PATH, [nodeId]); } } } From cec27939b1cdc3afbf8cc60d5dd4f34e156bc69c Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 15:00:27 +0300 Subject: [PATCH 08/18] chore: cleanup --- .../ConnectionsAdministrationService.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts index b4ba01bfd9..1f379929a1 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsAdministrationService.ts @@ -6,12 +6,10 @@ * you may not use this file except in compliance with the License. */ -import { AdministrationItemService, AdministrationItemType, AdministrationScreenService } from '@cloudbeaver/core-administration'; +import { AdministrationItemService, AdministrationItemType } from '@cloudbeaver/core-administration'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { GraphQLService } from '@cloudbeaver/core-sdk'; -import { ConnectionInfoResource } from '../../ConnectionInfoResource'; import { DBDriverResource } from '../../DBDriverResource'; import { ConnectionsResource } from '../ConnectionsResource'; import { ConnectionsAdministration } from './ConnectionsAdministration'; @@ -20,19 +18,15 @@ import { ConnectionsDrawerItem } from './ConnectionsDrawerItem'; @injectable() export class ConnectionsAdministrationService extends Bootstrap { constructor( - private administrationScreenService: AdministrationScreenService, private administrationItemService: AdministrationItemService, private notificationService: NotificationService, private connectionsResource: ConnectionsResource, - private connectionInfoResource: ConnectionInfoResource, private dbDriverResource: DBDriverResource, - private graphQLService: GraphQLService ) { super(); } register() { - this.administrationScreenService.activationEvent.addHandler((_, state) => this.handleDeactivate(state)); this.administrationItemService.create({ name: 'connections', @@ -53,14 +47,6 @@ export class ConnectionsAdministrationService extends Bootstrap { load(): void | Promise { } - private async handleDeactivate(state: boolean) { - // if (state) { - // return; - // } - - // this.connectionInfoResource.markOutdated(); - } - private async loadConnections() { try { await this.connectionsResource.loadAll(); From 3cf9a62a7e1904d7642d8475d144b4e54e77f0fc Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 15:07:40 +0300 Subject: [PATCH 09/18] fix(plugin-data-viewer): double update on chunk size change --- .../src/TableViewer/TableFooter/TableFooter.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx index f803a941bb..e300f22a84 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooter.tsx @@ -75,7 +75,16 @@ export const TableFooter = observer(function TableFooter({ }: TableFooterProps) { const ref = useRef(null); const handleChange = useCallback( - () => ref.current && model.setChunkSize(parseInt(ref.current.value, 10)), + () => { + if (!ref.current) { + return; + } + const value = parseInt(ref.current.value, 10); + + if (model.getChunkSize() !== value) { + model.setChunkSize(value); + } + }, [model] ); From f7333bce6cbec0f19a936080e28e88769b4fc13f Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Wed, 9 Sep 2020 15:20:39 +0300 Subject: [PATCH 10/18] CB-253 Connections refresh --- deploy/docker/docker-compose.yml | 1 + .../src/io/cloudbeaver/model/session/WebSession.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 562bfcb127..184356576e 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -7,3 +7,4 @@ services: - "8978:8978" volumes: - "./logs:/opt/cloudbeaver/logs" + - "./workspace:/opt/cloudbeaver/workspace" 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 caf215f32f..4513f5ec7f 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 @@ -206,7 +206,7 @@ public class WebSession implements DBASession { List connList = new ArrayList<>(); DBPDataSourceRegistry registry = databases.getDataSourceRegistry(); registry.refreshConfig(); - this.databases.refreshChildren(); + for (DBPDataSourceContainer ds : registry.getDataSources()) { if (ds.isProvided()) { WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds); From 8a906ff60d2099d4678c0863d7c63d9f11537b5f Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 15:26:43 +0300 Subject: [PATCH 11/18] fix(core-app): disable nav tree in configuration mode --- .../NodesManager/NavNodeManagerService.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts index 4360517e87..c52c715771 100644 --- a/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-app/src/shared/NodesManager/NavNodeManagerService.ts @@ -12,7 +12,9 @@ import { import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { IExecutor, Executor, IContextProvider } from '@cloudbeaver/core-executor'; -import { PermissionsService, EPermission, SessionResource } from '@cloudbeaver/core-root'; +import { + PermissionsService, EPermission, SessionResource, ServerService +} from '@cloudbeaver/core-root'; import { GraphQLService, resourceKeyList, isResourceKeyList, ResourceKey } from '@cloudbeaver/core-sdk'; @@ -98,6 +100,7 @@ export class NavNodeManagerService extends Bootstrap { private connectionAuthService: ConnectionAuthService, private notificationService: NotificationService, private sessionResource: SessionResource, + private serverService: ServerService, ) { super(); this.activeNavigationNodes = []; @@ -144,10 +147,11 @@ export class NavNodeManagerService extends Bootstrap { } async updateRootChildren() { - if (!await this.permissionsService.hasAsync(EPermission.public)) { + if (!await this.isNavTreeEnabled()) { this.navTree.delete(ROOT_NODE_PATH); return; } + await this.navTree.refresh(ROOT_NODE_PATH); } @@ -326,6 +330,10 @@ export class NavNodeManagerService extends Bootstrap { } private async connectionUpdateHandler(key: ResourceKey) { + if (!await this.isNavTreeEnabled()) { + return; + } + const keys = isResourceKeyList(key) ? key.list : [key]; for (const id of keys) { @@ -392,4 +400,18 @@ export class NavNodeManagerService extends Bootstrap { } } } + + private async isNavTreeEnabled() { + if (!await this.permissionsService.hasAsync(EPermission.public)) { + return false; + } + + // TODO: IT'S IS REALLY BAD PLACE FOR THAT + const config = await this.serverService.config.load(null); + if (config?.configurationMode) { + return false; + } + + return true; + } } From 3c7da0b03d51876ef5e5ffef40f18f08e83cd20b Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 15:47:02 +0300 Subject: [PATCH 12/18] chore: cleanup --- webapp/packages/core-bootstrap/src/AppBootstrap.ts | 2 -- .../src/AgGridTable/AgGridTableController.ts | 7 ------- 2 files changed, 9 deletions(-) diff --git a/webapp/packages/core-bootstrap/src/AppBootstrap.ts b/webapp/packages/core-bootstrap/src/AppBootstrap.ts index 1ac408b8ed..17546199db 100644 --- a/webapp/packages/core-bootstrap/src/AppBootstrap.ts +++ b/webapp/packages/core-bootstrap/src/AppBootstrap.ts @@ -12,12 +12,10 @@ import { NavigationTreeContextMenuService, LogViewerMenuService, ConnectionSchemaManagerService, - AppScreenService, } from '@cloudbeaver/core-app'; import { injectable } from '@cloudbeaver/core-di'; import { SessionExpireService } from '@cloudbeaver/core-dialogs'; import { ExceptionsCatcherService } from '@cloudbeaver/core-events'; -import { RouterService } from '@cloudbeaver/core-routing'; import { ThemeService } from '@cloudbeaver/core-theming'; /** diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts index 744166d363..af98a39f38 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts @@ -18,7 +18,6 @@ import { ColDef, ValueGetterParams, GridOptions, - CellEditingStoppedEvent, CellClassParams, SortChangedEvent, RowNode, @@ -74,7 +73,6 @@ export class AgGridTableController implements IInitializableController, IDestruc onGridReady: this.handleGridReady.bind(this), onBodyScroll: this.handleBodyScroll.bind(this), - onCellEditingStopped: this.handleCellEditingStopped.bind(this), onSortChanged: this.handleSortChanged.bind(this), }; @@ -163,11 +161,6 @@ export class AgGridTableController implements IInitializableController, IDestruc this.gridModel.onCellEditingStopped(rowIndex, colId, value, editing); } - private handleCellEditingStopped(event: CellEditingStoppedEvent) { - // TODO: probably no more needed because editCellValue executes on every change in editor - // this.gridModel.onCellEditingStopped(event.rowIndex, event.column.getColId(), event.value, false); - } - private isCellEdited(rowIndex: number, column: string) { return this.gridModel.isCellEdited(rowIndex, column); } From 1750b15a70648e034b9e3d66e07f8a3a44a6a6e6 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 15:48:26 +0300 Subject: [PATCH 13/18] fix(core-connections): driver custom properties CB-246 --- .../src/PropertiesTable/IProperty.ts | 1 + .../src/PropertiesTable/PropertiesTable.tsx | 2 +- .../src/PropertiesTable/PropertyItem.tsx | 4 ++-- .../DriverProperties/DriverProperties.tsx | 4 ++-- .../DriverPropertiesController.ts | 20 +++++++++++++++---- .../DriverProperties/DriverProperties.tsx | 4 ++-- .../DriverPropertiesController.ts | 19 ++++++++++++++---- 7 files changed, 39 insertions(+), 15 deletions(-) diff --git a/webapp/packages/core-blocks/src/PropertiesTable/IProperty.ts b/webapp/packages/core-blocks/src/PropertiesTable/IProperty.ts index 79076fdc00..e4dd204865 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/IProperty.ts +++ b/webapp/packages/core-blocks/src/PropertiesTable/IProperty.ts @@ -13,4 +13,5 @@ export interface IProperty { defaultValue?: string; description?: string; validValues?: string[]; + new?: boolean; } diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx index 331afe8f13..5fce925b32 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertiesTable.tsx @@ -105,7 +105,7 @@ export const PropertiesTable = observer(function PropertiesTable({ {translate('block_properties_table_value')} - {onAdd && } + {onAdd && } diff --git a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx index 6883c5ad8c..d4251ee281 100644 --- a/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx +++ b/webapp/packages/core-blocks/src/PropertiesTable/PropertyItem.tsx @@ -134,10 +134,10 @@ export const PropertyItem = observer(function PropertyItem({ const handleRemove = useCallback(() => onRemove(property.id), [property]); useLayoutEffect(() => { - if (keyInputRef.current && isKeyEditable) { + if (keyInputRef.current && isKeyEditable && property.new) { keyInputRef.current.focus(); } - }, []); + }, [property]); return styled(useStyles(styles))( diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverProperties.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverProperties.tsx index 9049b630e9..004588e5bb 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverProperties.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverProperties.tsx @@ -39,7 +39,7 @@ export const DriverProperties = observer(function DriverProperties({ state, loadProperties, }: DriverPropertiesProps) { - const controller = useController(DriverPropertiesController, driverId); + const controller = useController(DriverPropertiesController, driverId, state); useMemo(() => { if (loadProperties) { @@ -54,7 +54,7 @@ export const DriverProperties = observer(function DriverProperties({ )} diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts index 2d962886c0..e467d10628 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionForm/DriverProperties/DriverPropertiesController.ts @@ -32,20 +32,24 @@ export class DriverPropertiesController implements IInitializableController { loaded = false; + private state!: Record; + constructor( private driverPropertiesService: DriverPropertiesService, private notificationService: NotificationService ) { } - init(driverId: string) { + init(driverId: string, state: Record) { this.driverId = driverId; + this.state = state; } - onAddProperty = () => { + addProperty = (key?: string, value?: string) => { this.driverProperties.unshift({ id: uuid(), - key: 'property', - defaultValue: '', + key: key ?? 'property', + defaultValue: value ?? '', + new: !key, }); } @@ -64,6 +68,14 @@ export class DriverPropertiesController implements IInitializableController { description: property.description, validValues: property.validValues, }))); + + for (const key of Object.keys(this.state)) { + if (this.driverProperties.some(property => property.key === key)) { + continue; + } + + this.addProperty(key, this.state[key]); + } this.loaded = true; } catch (exception) { this.notificationService.logException(exception, 'Can\'t load driver properties'); diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx index e5b4b2714a..98978f1a40 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx @@ -41,7 +41,7 @@ export const DriverProperties = observer(function DriverProperties({ state, loadProperties, }: DriverPropertiesProps) { - const controller = useController(DriverPropertiesController, driver); + const controller = useController(DriverPropertiesController, driver, state); useEffect(() => { if (loadProperties) { @@ -56,7 +56,7 @@ export const DriverProperties = observer(function DriverProperties({ )} diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts index 9a1d574942..85a3ad9fc8 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts @@ -34,21 +34,24 @@ export class DriverPropertiesController implements IInitializableController { @observable driverProperties = observable([]) private loaded = false; + private state!: Record; constructor( private driverPropertiesService: DriverPropertiesService, private notificationService: NotificationService ) { } - init(driver: DBDriver) { + init(driver: DBDriver, state: Record) { this.driver = driver; + this.state = state; } - onAddProperty = () => { + addProperty = (key?: string, value?: string) => { this.driverProperties.unshift({ id: uuid(), - key: 'property', - defaultValue: '', + key: key ?? 'property', + defaultValue: value ?? '', + new: !key, }); } @@ -67,6 +70,14 @@ export class DriverPropertiesController implements IInitializableController { description: property.description, validValues: property.validValues, }))); + + for (const key of Object.keys(this.state)) { + if (this.driverProperties.some(property => property.key === key)) { + continue; + } + + this.addProperty(key, this.state[key]); + } this.loaded = true; } catch (exception) { this.notificationService.logException(exception, 'Can\'t load driver properties'); From 6fed7382690ce14cc8c7f201db614e18b4fe4f61 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 18:06:25 +0300 Subject: [PATCH 14/18] fix: table overflow CB-247 --- .../Connections/ConnectionsTable/ConnectionEdit.tsx | 1 + .../Connections/ConnectionsTable/ConnectionsTable.tsx | 3 +++ webapp/packages/core-theming/src/styles/_table.scss | 9 ++++++++- .../src/Administration/Users/UsersTable/UsersTable.tsx | 3 +++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx index 3c83bf1a29..45d025b48a 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx @@ -26,6 +26,7 @@ const styles = composes( `, css` box { + box-sizing: border-box; padding: 24px; min-height: 320px; max-height: 500px; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionsTable.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionsTable.tsx index 927d0ea02a..b857fdde6f 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionsTable.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionsTable.tsx @@ -25,6 +25,9 @@ const styles = composes( } `, css` + Table { + width: 100%; + } TableColumnHeader { border-top: solid 1px; } diff --git a/webapp/packages/core-theming/src/styles/_table.scss b/webapp/packages/core-theming/src/styles/_table.scss index c4edd9db1c..6ee29790cf 100644 --- a/webapp/packages/core-theming/src/styles/_table.scss +++ b/webapp/packages/core-theming/src/styles/_table.scss @@ -12,10 +12,15 @@ min-width: 100%; text-align: left; border-collapse: collapse; + table-layout: fixed; &[use|size="big"] { > thead > tr > th { height: 54px; + + &[use|min] { + width: 46px; + } } > tbody > tr > td { @@ -53,6 +58,7 @@ } th { + box-sizing: border-box; white-space: nowrap; padding: 16px; height: 36px; @@ -64,7 +70,7 @@ text-decoration: none !important; &[use|min] { - width: 1%; + width: 28px; } &:last-child { @@ -74,6 +80,7 @@ td { position: relative; + box-sizing: border-box; height: 28px; padding: 0 16px; transition: padding ease-in-out 0.24s; diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UsersTable.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UsersTable.tsx index 1cd3d78b9f..727e2b2972 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UsersTable.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UsersTable.tsx @@ -19,6 +19,9 @@ import { useStyles } from '@cloudbeaver/core-theming'; import { User } from './User'; const styles = css` + Table { + width: 100%; + } TableColumnHeader { border-top: solid 1px; } From 97f3a944a58c5a37330a3f55aa8f5278f61fd36b Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 19:01:42 +0300 Subject: [PATCH 15/18] fix(plugin-authentication): user creation CB-295 --- .../core-authentication/src/UsersResource.ts | 26 +-------- .../ConnectionsTable/ConnectionEdit.tsx | 6 -- .../src/Administration/Users/CreateUser.tsx | 8 +-- .../Users/UserForm/UserForm.tsx | 17 +++--- .../Users/UserForm/UserFormController.ts | 51 +++++++---------- .../Users/UsersAdministrationController.ts | 20 ++++--- .../Administration/Users/UsersTable/User.tsx | 12 +--- .../Users/UsersTable/UserEdit.tsx | 31 ++++++++++- .../Users/UsersTable/UserEditController.ts | 55 +++++++++++++++++++ 9 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index 6e2a34c652..5024886fed 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -26,7 +26,6 @@ type AdminUserNew = AdminUserInfo & { [NEW_USER_SYMBOL]: boolean } type UserCreateOptions = { userId: string; - newId?: string; roles: string[]; credentials: Record; grantedConnections: string[]; @@ -59,22 +58,6 @@ export class UsersResource extends CachedMapResource { return this.data.has(id); } - addNew() { - const user = { - userId: `new-${uuid()}`, - grantedRoles: [], - grantedConnections: [], - configurationParameters: {}, - metaParameters: {}, - [NEW_USER_SYMBOL]: true, - } as AdminUserNew; - - this.data.set(user.userId, user); - this.markUpdated(user.userId); - - return user; - } - async loadConnections(userId: string): Promise { if (this.isNew(userId)) { return []; @@ -90,15 +73,10 @@ export class UsersResource extends CachedMapResource { } async create({ - userId, newId, roles, credentials, grantedConnections, + userId, roles, credentials, grantedConnections, }: UserCreateOptions): Promise { const { user } = await this.graphQLService.gql.createUser({ userId }); - if (newId) { - this.data.delete(newId); - } - this.set(userId, user as AdminUserInfo); - try { await this.updateCredentials(userId, credentials); for (const roleId of roles) { @@ -106,6 +84,8 @@ export class UsersResource extends CachedMapResource { } await this.setConnections(userId, grantedConnections); + const user = await this.refresh(userId)as AdminUserNew; + user[NEW_USER_SYMBOL] = true; } catch (exception) { this.delete(userId); throw exception; diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx index 45d025b48a..2f946cd0a4 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionEdit.tsx @@ -33,12 +33,6 @@ const styles = composes( display: flex; flex-direction: column; } - - ErrorMessage { - position: sticky; - bottom: 0; - padding: 8px 24px; - } ` ); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx index 0ff36b77d6..9b00d72650 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/CreateUser.tsx @@ -28,13 +28,13 @@ const styles = composes( css` title-bar { composes: theme-typography--headline6 from global; - padding: 0 16px; + box-sizing: border-box; + padding: 16px 24px; border-top: solid 1px; align-items: center; - height: 48px; display: flex; font-weight: 400; - flex: 1; + flex: auto 0 0; } ` ); @@ -47,7 +47,7 @@ export function CreateUser({ return styled(useStyles(styles))( - + ); } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx index 9c5861ee45..210f58da14 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserForm.tsx @@ -21,6 +21,7 @@ import { } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { AdminUserInfo } from '@cloudbeaver/core-sdk'; import { useStyles, composes } from '@cloudbeaver/core-theming'; import { GrantedConnections } from './GrantedConnections'; @@ -53,10 +54,6 @@ const styles = composes( } `, css` - box { - padding: 24px; - } - SubmittingForm { flex: 1; display: flex; @@ -128,16 +125,18 @@ const styles = composes( ); type Props = { - userId: string; + user: AdminUserInfo; + editing?: boolean; onCancel: () => void; } export const UserForm = observer(function UserForm({ - userId, + user, + editing = false, onCancel, }: Props) { const translate = useTranslate(); - const controller = useController(UserFormController, userId, onCancel); + const controller = useController(UserFormController, user, editing, onCancel); const [focusedRef] = useFocus({ focusFirstChild: true }); const handleLoginChange = useCallback( @@ -182,7 +181,7 @@ export const UserForm = observer(function UserForm({ mod={['unelevated']} onClick={controller.save} > - {translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')} + {translate(!editing ? 'ui_processing_create' : 'ui_processing_save')} @@ -199,7 +198,7 @@ export const UserForm = observer(function UserForm({ name='login' value={controller.credentials.login} onChange={handleLoginChange} - disabled={!controller.isNew || controller.isSaving} + disabled={editing || controller.isSaving} mod='surface' required > diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts index feba6eb8b2..6edbc7a5f9 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserFormController.ts @@ -31,10 +31,6 @@ export class UserFormController implements IInitializableController, IDestructib roles: new Map(), }; - @computed get isNew() { - return this.usersResource.isNew(this.userId); - } - @computed get connections() { return Array.from(this.connectionsResource.data.values()) .filter(connection => !this.connectionsResource.isNew(connection.id)); @@ -44,16 +40,14 @@ export class UserFormController implements IInitializableController, IDestructib return Array.from(this.rolesManagerService.roles.data.values()); } - @computed get user(): AdminUserInfo { - return this.usersResource.get(this.userId)!; - } + user!: AdminUserInfo; readonly error = new GQLErrorCatcher(); private isDistructed = false; - private userId!: string; private connectionAccessChanged = false; private connectionAccessLoaded = false; private collapse!: () => void; + private editing!: boolean; constructor( private notificationService: NotificationService, @@ -64,8 +58,9 @@ export class UserFormController implements IInitializableController, IDestructib private dbDriverResource: DBDriverResource ) { } - init(id: string, collapse: () => void) { - this.userId = id; + init(user: AdminUserInfo, editing: boolean, collapse: () => void) { + this.user = user; + this.editing = editing; this.collapse = collapse; this.loadRoles(); } @@ -84,10 +79,9 @@ export class UserFormController implements IInitializableController, IDestructib this.isSaving = true; try { - if (this.isNew) { + if (!this.editing) { await this.usersResource.create({ userId: this.credentials.login, - newId: this.userId, credentials: { password: this.credentials.password }, roles: this.getGrantedRoles(), grantedConnections: this.getGrantedConnections(), @@ -103,12 +97,9 @@ export class UserFormController implements IInitializableController, IDestructib await this.usersResource.refresh(this.user.userId); this.notificationService.logInfo({ title: 'authentication_administration_user_updated' }); } - - this.connectionAccessLoaded = false; - await this.loadConnectionsAccess(); } catch (exception) { if (!this.error.catch(exception) || this.isDistructed) { - if (this.isNew) { + if (!this.editing) { this.notificationService.logException(exception, 'Error creating new user'); } else { this.notificationService.logException(exception, 'Error saving user'); @@ -134,12 +125,14 @@ export class UserFormController implements IInitializableController, IDestructib this.isLoading = true; try { - this.grantedConnections = await this.usersResource.loadConnections(this.userId); + if (this.editing) { + this.grantedConnections = await this.usersResource.loadConnections(this.user.userId); - this.selectedConnections.clear(); - for (const connection of this.grantedConnections) { - if (connection.subjectType !== AdminSubjectType.Role) { - this.selectedConnections.set(connection.connectionId, true); + this.selectedConnections.clear(); + for (const connection of this.grantedConnections) { + if (connection.subjectType !== AdminSubjectType.Role) { + this.selectedConnections.set(connection.connectionId, true); + } } } this.connectionAccessLoaded = true; @@ -161,12 +154,12 @@ export class UserFormController implements IInitializableController, IDestructib return; } - if (!this.credentials.password && this.isNew) { + if (!this.credentials.password && !this.editing) { this.notificationService.logError({ title: 'authentication_user_password_not_set' }); return; } - if (!this.credentials.password && this.isNew) { + if (!this.credentials.password && !this.editing) { this.notificationService.logError({ title: 'authentication_user_password_not_set' }); return; } @@ -190,7 +183,7 @@ export class UserFormController implements IInitializableController, IDestructib if (!this.user.grantedRoles.includes(roleId)) { await this.usersResource.grantRole(this.user.userId, roleId, true); } - } else if (!this.isNew) { + } else { await this.usersResource.revokeRole(this.user.userId, roleId, true); } } @@ -215,8 +208,10 @@ export class UserFormController implements IInitializableController, IDestructib if (!this.connectionAccessChanged) { return; } - await this.usersResource.setConnections(this.userId, this.getGrantedConnections()); + await this.usersResource.setConnections(this.user.userId, this.getGrantedConnections()); this.connectionAccessChanged = false; + this.connectionAccessLoaded = false; + await this.loadConnectionsAccess(); } private async loadRoles() { @@ -232,11 +227,7 @@ export class UserFormController implements IInitializableController, IDestructib private async loadUser() { try { - await this.usersResource.load(this.userId); - - if (!this.isNew) { - this.credentials.login = this.user.userId; - } + this.credentials.login = this.user.userId; this.credentials.roles = new Map(this.user.grantedRoles.map(roleId => ([roleId, true]))); } catch (exception) { this.notificationService.logException(exception, 'Can\'t load user'); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts index 8df7843c18..351e00765a 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersAdministrationController.ts @@ -13,7 +13,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications'; -import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk'; +import { GQLErrorCatcher, resourceKeyList, AdminUserInfo } from '@cloudbeaver/core-sdk'; import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService'; @@ -26,10 +26,9 @@ export class UsersAdministrationController { @computed get users() { return Array.from(this.usersResource.data.values()) - .filter(user => !this.usersResource.isNew(user.userId)) .sort((a, b) => { if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) { - return 0; + return a.userId.localeCompare(b.userId); } if (this.usersResource.isNew(a.userId)) { return -1; @@ -38,10 +37,7 @@ export class UsersAdministrationController { }); } - @computed get creatingUser() { - return Array.from(this.usersResource.data.values()) - .find(user => this.usersResource.isNew(user.userId)); - } + @observable creatingUser: AdminUserInfo | null = null; get isLoading() { return this.usersResource.isLoading() || this.isDeleting; @@ -55,7 +51,13 @@ export class UsersAdministrationController { ) { } create = () => { - this.usersResource.addNew(); + this.creatingUser = { + userId: '', + grantedRoles: [], + grantedConnections: [], + configurationParameters: {}, + metaParameters: {}, + } as AdminUserInfo; this.usersAdministrationNavigationService.navToAdd(); } @@ -64,7 +66,7 @@ export class UsersAdministrationController { return; } - this.usersResource.delete(this.creatingUser.userId); + this.creatingUser = null; this.usersAdministrationNavigationService.navToRoot(); } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx index 760f123893..2f0a814f10 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/User.tsx @@ -7,14 +7,11 @@ */ import { observer } from 'mobx-react'; -import styled, { css, use } from 'reshadow'; +import styled, { css } from 'reshadow'; -import { UsersResource } from '@cloudbeaver/core-authentication'; import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { useTranslate } from '@cloudbeaver/core-localization'; import { AdminUserInfo } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -31,9 +28,6 @@ type Props = { } export const User = observer(function User({ user }: Props) { - const translate = useTranslate(); - const usersResource = useService(UsersResource); - const isNew = usersResource.isNew(user.userId); return styled(useStyles(styles))( @@ -43,9 +37,9 @@ export const User = observer(function User({ user }: Props) { - {isNew ? translate('authentication_administration_user_connections_user_new') : user.userId} + {user.userId} {user.grantedRoles.join(', ')} - {isNew && {translate('ui_tag_new')}} + ); }); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx index ab38706268..3146c39f2c 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEdit.tsx @@ -8,12 +8,34 @@ import { observer } from 'mobx-react'; import { useContext, useCallback } from 'react'; +import styled, { css } from 'reshadow'; import { TableContext, } from '@cloudbeaver/core-blocks'; +import { useController } from '@cloudbeaver/core-di'; +import { composes, useStyles } from '@cloudbeaver/core-theming'; import { UserForm } from '../UserForm/UserForm'; +import { UserEditController } from './UserEditController'; + +const styles = composes( + css` + box { + composes: theme-background-secondary theme-text-on-secondary from global; + } + `, + css` + box { + box-sizing: border-box; + padding: 24px; + min-height: 320px; + max-height: 500px; + display: flex; + flex-direction: column; + } + ` +); type Props = { item: string; @@ -22,8 +44,15 @@ type Props = { export const UserEdit = observer(function UserEdit({ item, }: Props) { + const controller = useController(UserEditController, item); const tableContext = useContext(TableContext); const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]); - return ; + return styled(useStyles(styles))( + + {controller.user && ( + + )} + + ); }); diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts new file mode 100644 index 0000000000..6210dbd87c --- /dev/null +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UsersTable/UserEditController.ts @@ -0,0 +1,55 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 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 { observable, computed } from 'mobx'; + +import { UsersResource } from '@cloudbeaver/core-authentication'; +import { + injectable, IInitializableController, IDestructibleController +} from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { GQLErrorCatcher, AdminUserInfo } from '@cloudbeaver/core-sdk'; + +@injectable() +export class UserEditController +implements IInitializableController, IDestructibleController { + @observable isLoading = true; + @observable user: AdminUserInfo | null = null; + + @computed get isDisabled() { + return this.isLoading; + } + + userId!: string; + + readonly error = new GQLErrorCatcher(); + + constructor( + private notificationService: NotificationService, + private usersResource: UsersResource, + ) { } + + init(id: string) { + this.userId = id; + this.loadUser(); + } + + destruct(): void { } + + private async loadUser() { + this.isLoading = true; + try { + // we create a copy to protect the current value from mutation + this.user = JSON.parse(JSON.stringify(await this.usersResource.load(this.userId))); + } catch (exception) { + this.notificationService.logException(exception, `Can't load user ${this.userId}`); + } finally { + this.isLoading = false; + } + } +} From 91834c9c7df8e62badc0ff1f36eb412c4e1797eb Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 9 Sep 2020 19:15:31 +0300 Subject: [PATCH 16/18] feat(core-administration): localization CB-293 --- .../Pages/Finish/FinishPage.tsx | 9 +++++++-- .../ServerConfigurationPage.tsx | 10 ++++++++-- .../Pages/Welcome/WelcomePage.tsx | 14 ++++++++++++-- .../core-administration/src/locales/en.ts | 11 +++++++++-- .../Connections/ConnectionsAdministration.tsx | 18 +++++++++++++++++- .../core-connections/src/locales/en.ts | 1 + 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/Finish/FinishPage.tsx b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/Finish/FinishPage.tsx index c19974014d..0e851d08ae 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/Finish/FinishPage.tsx +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/Finish/FinishPage.tsx @@ -41,6 +41,11 @@ const styles = composes( border: solid 1px; padding: 16px 24px } + + p { + line-height: 2; + white-space: pre; + } ` ); @@ -52,8 +57,8 @@ export const FinishPage = observer(function FinishPage() { -

All done

-

Go to fun

+

{translate('administration_configuration_wizard_finish_title')}

+

{translate('administration_configuration_wizard_finish_message')}