From dec3c37e642c44e8c75c8f0a98f07875a9c787bc Mon Sep 17 00:00:00 2001 From: Ainur <59531286+yagudin10@users.noreply.github.com> Date: Thu, 26 Jan 2023 12:58:08 +0100 Subject: [PATCH] CB-2881 remove public permission (#1414) * CB-2881 remove public permission * CB-3035 add authRequired param for WebAction * CB-3035 add authRequired param for WebAction * CB-3035 add authRequired param for WebAction * CB-2881 refactor: replace public permission with authentication check * CB-3035 empty user permissions fix * CB-2881 fix: validate resource keys * CB-3035 user with no teams login fix * CB-2881 remove user teams validation * CB-2881 fix: check is connection is available in ResourceSqlDataSource Co-authored-by: Aleksey Potsetsuev Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> --- config/core/initial-data.conf | 4 +- .../src/io/cloudbeaver/DBWConstants.java | 1 - .../session/WebSessionAuthProcessor.java | 3 -- .../model/session/WebUserContext.java | 4 -- .../bundles/io.cloudbeaver.server/plugin.xml | 2 - .../src/io/cloudbeaver/WebAction.java | 4 +- .../src/io/cloudbeaver/WebActionSet.java | 2 +- .../service/WebServiceBindingBase.java | 3 +- .../service/core/DBWServiceCore.java | 18 +++---- .../service/core/impl/WebServiceCore.java | 1 - .../service/auth/DBWServiceAuth.java | 10 ++-- .../CBEmbeddedSecurityController.java | 2 +- .../workspace/conf/initial-data.conf | 4 +- .../src/PermissionsResource.ts | 9 +++- .../src/useAdministrationSettings.ts | 17 ++----- .../core-authentication/src/AppAuthService.ts | 44 ++++++++++++----- .../AuthConfigurationParametersResource.ts | 7 +++ .../src/AuthConfigurationsResource.ts | 7 +++ .../src/AuthProvidersResource.ts | 7 +++ .../core-authentication/src/TeamsResource.ts | 7 +++ .../src/UserMetaParametersResource.ts | 2 +- .../core-authentication/src/UsersResource.ts | 7 +++ .../src/ConnectionDialectResource.ts | 14 +++++- .../ConnectionExecutionContextResource.ts | 15 ++++-- .../src/ConnectionFolderResource.ts | 18 +++++-- .../src/ConnectionInfoResource.ts | 20 ++++++-- .../core-connections/src/ContainerResource.ts | 18 +++++-- .../core-connections/src/DBDriverResource.ts | 14 ++++-- .../src/DatabaseAuthModelsResource.ts | 10 +++- .../src/NetworkHandlerResource.ts | 12 ++++- .../src/NodesManager/DBObjectResource.ts | 7 +++ .../src/NodesManager/NavNodeInfoResource.ts | 13 +++-- .../src/NodesManager/NavTreeResource.ts | 15 ++++-- .../core-projects/src/ProjectInfoResource.ts | 16 ++++-- .../src/ProjectPermissionsResource.ts | 2 +- .../src/ResourceManagerResource.ts | 12 +++++ .../src/SharedProjectsResource.ts | 7 +++ .../core-root/src/FeaturesResource.ts | 2 +- .../core-root/src/PermissionsService.ts | 1 - .../core-root/src/ServerConfigResource.ts | 22 ++++++++- .../src/Resource/CachedDataResource.ts | 12 ++++- .../src/Resource/CachedMapResource.ts | 29 ++++++----- .../core-sdk/src/Resource/CachedResource.ts | 20 +++++++- .../src/Resource/CachedResourceIncludes.ts | 49 +++++++++++-------- .../core-sdk/src/Resource/ResourceKeyList.ts | 4 ++ .../src/SideBarPanel/SideBarPanelService.ts | 2 +- webapp/packages/core-ui/src/Tabs/TabList.tsx | 4 +- .../src/Tabs/TabsContainer/ITabsContainer.ts | 1 + .../src/Tabs/TabsContainer/TabsContainer.ts | 4 +- .../packages/core-ui/src/Tabs/TabsState.tsx | 6 +-- .../core-version/src/VersionResource.ts | 9 +++- .../src/DataContext/useDynamicDataContext.ts | 4 +- .../AdministrationMenu/AdministrationMenu.tsx | 8 +-- .../Form/ServerConfigurationFeaturesForm.tsx | 3 +- .../ServerConfigurationService.ts | 2 +- .../AuthConfigurationFormService.ts | 8 +-- ...verConfigurationAuthenticationBootstrap.ts | 4 +- .../Users/Teams/TeamFormService.ts | 2 +- .../Users/UserForm/UserFormService.ts | 2 +- .../Users/UsersAdministration.tsx | 2 +- .../src/AuthenticationService.ts | 4 +- .../src/TemplateConnectionPluginBootstrap.ts | 7 ++- .../src/TemplateConnectionsResource.ts | 9 ++-- .../Connections/CreateConnectionService.ts | 2 +- .../ConnectionForm/ConnectionFormService.ts | 2 +- .../Options/ConnectionOptionsTabService.ts | 2 +- .../connectionFormConfigureContext.ts | 4 +- .../src/DataTransferProcessorsResource.ts | 2 +- .../src/Dialog/ProcessorConfigureDialog.tsx | 2 +- .../TablePresentationBar.tsx | 2 +- .../ValuePanel/DataValuePanelService.ts | 2 +- .../TextValue/TextValuePresentationService.ts | 4 +- .../ConnectionSchemaManagerBootstrap.ts | 31 +++++++++--- .../ConnectionSelector/ConnectionIcon.tsx | 6 +-- .../src/SessionLogsResource.ts | 7 +-- .../ElementsTree/useElementsTree.ts | 7 +-- .../src/NavigationTree/NavigationTree.tsx | 8 +-- .../SessionExpireWarningDialogService.ts | 2 +- .../src/ResourceSqlDataSource.ts | 11 ++++- .../src/ResourceSqlDataSourceBootstrap.ts | 3 ++ .../src/SqlEditorTabService.ts | 15 +++--- .../src/SqlEditorModeService.ts | 2 +- .../SqlGenerators/SqlGeneratorsResource.ts | 7 +++ .../src/SqlResultTabs/SqlResultTabs.tsx | 2 +- .../src/ToolsPanel/ToolsPanel.tsx | 4 +- .../src/ToolsPanel/ToolsPanelService.ts | 2 +- .../TopNavBar/AppStateMenu/AppStateMenu.tsx | 8 +-- .../src/TopNavBar/MainMenu/MainMenu.tsx | 8 +-- .../src/UserProfileForm/UserForm.tsx | 2 +- 89 files changed, 506 insertions(+), 219 deletions(-) diff --git a/config/core/initial-data.conf b/config/core/initial-data.conf index fe8f9db507..b3898fb45f 100644 --- a/config/core/initial-data.conf +++ b/config/core/initial-data.conf @@ -4,13 +4,13 @@ subjectId: "admin", name: "Admin", description: "Administrative access. Has all permissions.", - permissions: [ "public", "admin" ] + permissions: [ "admin" ] }, { subjectId: "user", name: "User", description: "Standard user", - permissions: [ "public" ] + permissions: [ ] } ] } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java index 367c6d4ae7..dcacfed318 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/DBWConstants.java @@ -23,7 +23,6 @@ import org.jkiss.dbeaver.model.access.DBAPermissionRealm; */ public class DBWConstants { - public static final String PERMISSION_PUBLIC = DBAPermissionRealm.PERMISSION_PUBLIC; public static final String PERMISSION_ADMIN = DBAPermissionRealm.PERMISSION_ADMIN; public static final String PERMISSION_CONFIGURATION_MANAGER = "configuration-manager"; diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java index 4a7f2b03a6..48f6e6cd83 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java @@ -161,9 +161,6 @@ public class WebSessionAuthProcessor { providerConfig, authAttrs); - if (!configMode && securityController.getUserPermissions(userId).isEmpty()) { - throw new DBWebException("Access denied (no permissions)"); - } if (!configMode && !securityController.getCurrentUser().isEnabled()) { throw new DBWebException("User account is locked"); } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java index 2ec891d953..cb771459d5 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java @@ -202,10 +202,6 @@ public class WebUserContext implements SMCredentialsProvider { private void setUserPermissions(Set permissions) { this.userPermissions = permissions; - // FIXME: automatically assign public permission in sm controller˚ - if (!CommonUtils.isEmpty(userPermissions)) { - userPermissions.add(DBWConstants.PERMISSION_PUBLIC); - } } public DBSSecretController getSecretController() { diff --git a/server/bundles/io.cloudbeaver.server/plugin.xml b/server/bundles/io.cloudbeaver.server/plugin.xml index 17a19e2c37..997cd9cbab 100644 --- a/server/bundles/io.cloudbeaver.server/plugin.xml +++ b/server/bundles/io.cloudbeaver.server/plugin.xml @@ -21,8 +21,6 @@ - impleme } CBApplication application = CBApplication.getInstance(); if (!application.isConfigurationMode()) { - Set sessionPermissions = session.getSessionPermissions(); - if (CommonUtils.isEmpty(sessionPermissions)) { + if (webAction.authRequired() && !session.isAuthorizedInSecurityManager()) { log.debug("Anonymous access to " + method.getName() + " restricted"); 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 60c255e590..f4ceeaefa1 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 @@ -38,7 +38,7 @@ import java.util.Map; */ public interface DBWServiceCore extends DBWService { - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebServerConfig getServerConfig() throws DBWebException; @WebAction @@ -50,14 +50,14 @@ public interface DBWServiceCore extends DBWService { @WebAction List getNetworkHandlers(@NotNull WebSession webSession); - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) List getUserConnections( @NotNull WebSession webSession, @Nullable String projectId, @Nullable String id, @Nullable List projectIds) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) List getConnectionFolders( @NotNull WebSession webSession, @Nullable String projectId, @Nullable String id) throws DBWebException; @@ -68,32 +68,32 @@ public interface DBWServiceCore extends DBWService { @WebAction List getTemplateConnections(@NotNull WebSession webSession, @Nullable String projectId) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) String[] getSessionPermissions(@NotNull WebSession webSession) throws DBWebException; /////////////////////////////////////////// // Session - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebSession openSession( @NotNull WebSession webSession, @Nullable String defaultLocale, @NotNull HttpServletRequest servletRequest, @NotNull HttpServletResponse servletResponse) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebSession getSessionState(@NotNull WebSession webSession) throws DBWebException; @WebAction List readSessionLog(@NotNull WebSession webSession, Integer maxEntries, Boolean clearEntries) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) boolean closeSession(HttpServletRequest request) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) boolean touchSession(@NotNull HttpServletRequest request, @NotNull HttpServletResponse servletResponse) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) boolean refreshSessionConnections(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response) throws DBWebException; @WebAction 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 7d563076ce..a234fc37bf 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 @@ -208,7 +208,6 @@ public class WebServiceCore implements DBWServiceCore { public String[] getSessionPermissions(@NotNull WebSession webSession) throws DBWebException { if (CBApplication.getInstance().isConfigurationMode()) { return new String[] { - DBWConstants.PERMISSION_PUBLIC, DBWConstants.PERMISSION_ADMIN }; } 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 5fc99b3030..feee6424f0 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 @@ -32,7 +32,7 @@ import java.util.Map; */ public interface DBWServiceAuth extends DBWService { - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebAuthStatus authLogin( @NotNull WebSession webSession, @NotNull String providerId, @@ -41,16 +41,16 @@ public interface DBWServiceAuth extends DBWService { boolean linkWithActiveUser) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebAuthStatus authUpdateStatus(@NotNull WebSession webSession, @NotNull String authId, boolean linkWithActiveUser) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) void authLogout(@NotNull WebSession webSession, @Nullable String providerId, @Nullable String configurationId) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebUserInfo activeUser(@NotNull WebSession webSession) throws DBWebException; - @WebAction(requirePermissions = {}) + @WebAction(authRequired = false) WebAuthProviderInfo[] getAuthProviders(); @WebAction() diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index a9a99c72bf..864e877367 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -821,7 +821,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } protected String[] getDefaultTeamPermissions() { - return new String[]{DBWConstants.PERMISSION_PUBLIC}; + return new String[0]; } @Override diff --git a/server/test/io.cloudbeaver.test.platform/workspace/conf/initial-data.conf b/server/test/io.cloudbeaver.test.platform/workspace/conf/initial-data.conf index 1aa23bdcc3..b23e2346c4 100644 --- a/server/test/io.cloudbeaver.test.platform/workspace/conf/initial-data.conf +++ b/server/test/io.cloudbeaver.test.platform/workspace/conf/initial-data.conf @@ -6,13 +6,13 @@ subjectId: "admin", name: "Admin", description: "Administrative access. Has all permissions.", - permissions: [ "public", "admin" ] + permissions: [ "admin" ] }, { subjectId: "user", name: "User", description: "Standard user", - permissions: [ "public" ] + permissions: [ ] } ] } diff --git a/webapp/packages/core-administration/src/PermissionsResource.ts b/webapp/packages/core-administration/src/PermissionsResource.ts index 1f7c496020..574ae6e6a9 100644 --- a/webapp/packages/core-administration/src/PermissionsResource.ts +++ b/webapp/packages/core-administration/src/PermissionsResource.ts @@ -8,7 +8,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { SessionDataResource } from '@cloudbeaver/core-root'; -import { GraphQLService, CachedMapResource, CachedMapAllKey, AdminPermissionInfoFragment, AdminObjectGrantInfoFragment } from '@cloudbeaver/core-sdk'; +import { GraphQLService, CachedMapResource, CachedMapAllKey, AdminPermissionInfoFragment, AdminObjectGrantInfoFragment, ResourceKey } from '@cloudbeaver/core-sdk'; export type PermissionInfo = AdminPermissionInfoFragment; export type AdminObjectGrantInfo = AdminObjectGrantInfoFragment; @@ -34,4 +34,11 @@ export class PermissionsResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-administration/src/useAdministrationSettings.ts b/webapp/packages/core-administration/src/useAdministrationSettings.ts index 8671fd9752..bdc89289d2 100644 --- a/webapp/packages/core-administration/src/useAdministrationSettings.ts +++ b/webapp/packages/core-administration/src/useAdministrationSettings.ts @@ -6,10 +6,8 @@ * you may not use this file except in compliance with the License. */ -import { usePermission } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { ServerService } from '@cloudbeaver/core-root'; -import type { ServerConfig } from '@cloudbeaver/core-sdk'; +import { usePermission, useResource } from '@cloudbeaver/core-blocks'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; import { EAdminPermission } from './EAdminPermission'; @@ -17,7 +15,7 @@ interface IAdministrationSettings { credentialsSavingEnabled: boolean; } -function getCredentialsSavingSetting(config: ServerConfig, isAdmin: boolean) { +function getCredentialsSavingSetting(config: ServerConfigResource, isAdmin: boolean) { if (config.configurationMode) { return true; } @@ -35,14 +33,9 @@ function getCredentialsSavingSetting(config: ServerConfig, isAdmin: boolean) { export function useAdministrationSettings(): IAdministrationSettings { const isAdmin = usePermission(EAdminPermission.admin); - const serverService = useService(ServerService); - const config = serverService.config.data; - - if (!config) { - throw new Error("Can't get credentials save permission"); - } + const { resource: serverConfigResource } = useResource(useAdministrationSettings, ServerConfigResource, undefined); return { - credentialsSavingEnabled: getCredentialsSavingSetting(config, isAdmin), + credentialsSavingEnabled: getCredentialsSavingSetting(serverConfigResource, isAdmin), }; } diff --git a/webapp/packages/core-authentication/src/AppAuthService.ts b/webapp/packages/core-authentication/src/AppAuthService.ts index aa917e0004..68d988894d 100644 --- a/webapp/packages/core-authentication/src/AppAuthService.ts +++ b/webapp/packages/core-authentication/src/AppAuthService.ts @@ -7,28 +7,36 @@ */ import { injectable, Bootstrap } from '@cloudbeaver/core-di'; -import { Executor, IExecutor } from '@cloudbeaver/core-executor'; -import { ServerService } from '@cloudbeaver/core-root'; +import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { CachedDataResourceParam, CachedResource, getCachedDataResourceLoaderState } from '@cloudbeaver/core-sdk'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import { UserInfoResource } from './UserInfoResource'; @injectable() export class AppAuthService extends Bootstrap { get authenticated(): boolean { - const config = this.serverService.config.data; const user = this.userInfoResource.data; return ( - !!config?.anonymousAccessEnabled - || this.serverService.config.configurationMode + this.serverConfigResource.anonymousAccessEnabled + || this.serverConfigResource.configurationMode || user !== null ); } + get loaders(): ILoadableState[] { + return [ + getCachedDataResourceLoaderState(this.userInfoResource, undefined), + getCachedDataResourceLoaderState(this.serverConfigResource, undefined), + ]; + } + readonly auth: IExecutor; constructor( - private readonly serverService: ServerService, + private readonly serverConfigResource: ServerConfigResource, private readonly userInfoResource: UserInfoResource, ) { super(); @@ -36,21 +44,35 @@ export class AppAuthService extends Bootstrap { this.userInfoResource.onDataUpdate.addHandler(this.authUser.bind(this)); } + requireAuthentication>( + resource: CachedResource, + map?: (param: T | undefined) => T + ): this { + resource + .preloadResource(this.userInfoResource, () => {}) + .preloadResource(this.serverConfigResource, () => {}) + .before(ExecutorInterrupter.interrupter(() => !this.authenticated)); + + this.userInfoResource.outdateResource(resource, map as any); + + return this; + } + async isAuthNeeded(): Promise { - const config = await this.serverService.config.load(); + const config = await this.serverConfigResource.load(); if (!config) { throw new Error('Can\'t configure Authentication'); } - const user = await this.userInfoResource.load(undefined, []); + const user = await this.userInfoResource.load(); - return !this.serverService.config.configurationMode - && !config.anonymousAccessEnabled + return !this.serverConfigResource.configurationMode + && !this.serverConfigResource.anonymousAccessEnabled && user === null; } async authUser(): Promise { - const userInfo = await this.userInfoResource.load(undefined, []); + const userInfo = await this.userInfoResource.load(); const state = userInfo !== null; await this.auth.execute(state); diff --git a/webapp/packages/core-authentication/src/AuthConfigurationParametersResource.ts b/webapp/packages/core-authentication/src/AuthConfigurationParametersResource.ts index 535365214d..e7741e9658 100644 --- a/webapp/packages/core-authentication/src/AuthConfigurationParametersResource.ts +++ b/webapp/packages/core-authentication/src/AuthConfigurationParametersResource.ts @@ -46,4 +46,11 @@ export class AuthConfigurationParametersResource return this.data; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-authentication/src/AuthConfigurationsResource.ts b/webapp/packages/core-authentication/src/AuthConfigurationsResource.ts index 2a996b917c..0b941b1379 100644 --- a/webapp/packages/core-authentication/src/AuthConfigurationsResource.ts +++ b/webapp/packages/core-authentication/src/AuthConfigurationsResource.ts @@ -111,6 +111,13 @@ export class AuthConfigurationsResource return key; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } function isNewConfiguration( diff --git a/webapp/packages/core-authentication/src/AuthProvidersResource.ts b/webapp/packages/core-authentication/src/AuthProvidersResource.ts index bce956ffbf..e13edfd715 100644 --- a/webapp/packages/core-authentication/src/AuthProvidersResource.ts +++ b/webapp/packages/core-authentication/src/AuthProvidersResource.ts @@ -151,4 +151,11 @@ export class AuthProvidersResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-authentication/src/TeamsResource.ts b/webapp/packages/core-authentication/src/TeamsResource.ts index ba3c8bb42d..351ec531c7 100644 --- a/webapp/packages/core-authentication/src/TeamsResource.ts +++ b/webapp/packages/core-authentication/src/TeamsResource.ts @@ -185,6 +185,13 @@ export class TeamsResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } function isNewTeam(team: TeamInfo | NewTeam): team is NewTeam { diff --git a/webapp/packages/core-authentication/src/UserMetaParametersResource.ts b/webapp/packages/core-authentication/src/UserMetaParametersResource.ts index ad8566427d..9aa6e807e8 100644 --- a/webapp/packages/core-authentication/src/UserMetaParametersResource.ts +++ b/webapp/packages/core-authentication/src/UserMetaParametersResource.ts @@ -32,7 +32,7 @@ export class UserMetaParametersResource extends CachedDataResource {}, () => {}); this - .preloadResource(userInfoResource) + .preloadResource(userInfoResource, () => {}) .before(ExecutorInterrupter.interrupter(() => userInfoResource.data === null)); } diff --git a/webapp/packages/core-authentication/src/UsersResource.ts b/webapp/packages/core-authentication/src/UsersResource.ts index 481e783ee6..8142ba19f5 100644 --- a/webapp/packages/core-authentication/src/UsersResource.ts +++ b/webapp/packages/core-authentication/src/UsersResource.ts @@ -251,6 +251,13 @@ export class UsersResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } export function isLocalUser(user: AdminUser): boolean { diff --git a/webapp/packages/core-connections/src/ConnectionDialectResource.ts b/webapp/packages/core-connections/src/ConnectionDialectResource.ts index fb1ab084a0..c407ab1657 100644 --- a/webapp/packages/core-connections/src/ConnectionDialectResource.ts +++ b/webapp/packages/core-connections/src/ConnectionDialectResource.ts @@ -17,7 +17,8 @@ import { ResourceKeyUtils, CachedMapAllKey, resourceKeyList, - SqlDialectInfo + SqlDialectInfo, + isResourceKeyList } from '@cloudbeaver/core-sdk'; import type { IConnectionExecutionContextInfo } from './ConnectionExecutionContext/IConnectionExecutionContextInfo'; @@ -106,4 +107,15 @@ export class ConnectionDialectResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || ( + typeof param === 'object' && !isResourceKeyList(param) + && typeof param.projectId === 'string' + && ['string'].includes(typeof param.connectionId) + ) + ); + } } diff --git a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts index c2f3556bf2..8ef44d69e6 100644 --- a/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts +++ b/webapp/packages/core-connections/src/ConnectionExecutionContext/ConnectionExecutionContextResource.ts @@ -8,8 +8,8 @@ import { action, makeObservable, runInAction } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, SessionPermissionsResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -40,7 +40,7 @@ export class ConnectionExecutionContextResource extends CachedMapResource a.mark === b.mark ); - permissionsResource - .require(this, EPermission.public) - .outdateResource(this); + appAuthService.requireAuthentication(this); connectionInfoResource.onItemAdd.addHandler(this.updateConnectionContexts.bind(this)); connectionInfoResource.onItemDelete.addHandler(this.deleteConnectionContexts.bind(this)); @@ -242,6 +240,13 @@ export class ConnectionExecutionContextResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } function getBaseContext(context: SqlContextInfo): IConnectionExecutionContextInfo { diff --git a/webapp/packages/core-connections/src/ConnectionFolderResource.ts b/webapp/packages/core-connections/src/ConnectionFolderResource.ts index e6e792cda0..1fcad862f6 100644 --- a/webapp/packages/core-connections/src/ConnectionFolderResource.ts +++ b/webapp/packages/core-connections/src/ConnectionFolderResource.ts @@ -8,8 +8,9 @@ import { action, makeObservable, runInAction } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, SessionPermissionsResource, SessionDataResource } from '@cloudbeaver/core-root'; +import { SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -42,11 +43,11 @@ export class ConnectionFolderResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || ( + typeof param === 'object' && !isResourceKeyList(param) + && typeof param.projectId === 'string' + && ['string'].includes(typeof param.folderId) + ) + ); + } } function isConnectionFolderProjectKey( diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index ee76ce3c1a..19798b302c 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -8,10 +8,11 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { SyncExecutor, ExecutorInterrupter, ISyncExecutor } from '@cloudbeaver/core-executor'; import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects'; -import { EPermission, NavigatorViewSettings, SessionPermissionsResource, SessionDataResource, DataSynchronizationService, ServerEventId } from '@cloudbeaver/core-root'; +import { NavigatorViewSettings, SessionDataResource, DataSynchronizationService, ServerEventId } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -83,7 +84,7 @@ export class ConnectionInfoResource private readonly projectInfoResource: ProjectInfoResource, private readonly dataSynchronizationService: DataSynchronizationService, sessionDataResource: SessionDataResource, - permissionsResource: SessionPermissionsResource, + appAuthService: AppAuthService, connectionInfoEventHandler: ConnectionInfoEventHandler, ) { super(); @@ -114,7 +115,7 @@ export class ConnectionInfoResource this.onItemDelete.addHandler(ExecutorInterrupter.interrupter(() => this.sessionUpdate)); this.onConnectionCreate.addHandler(ExecutorInterrupter.interrupter(() => this.sessionUpdate)); - permissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); this.sync(this.projectInfoResource, () => CachedMapAllKey, () => CachedMapAllKey); this.projectsService.onActiveProjectChange.addHandler(data => { if (data.type === 'after') { @@ -594,6 +595,19 @@ export class ConnectionInfoResource customIncludeOptions: false, }; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || param === connectionInfoProjectKeySymbol + || param === connectionInfoActiveProjectKeySymbol + || ( + typeof param === 'object' && !isResourceKeyList(param) + && typeof param.projectId === 'string' + && ['string'].includes(typeof param.connectionId) + ) + ); + } } function isConnectionInfoProjectKey( diff --git a/webapp/packages/core-connections/src/ContainerResource.ts b/webapp/packages/core-connections/src/ContainerResource.ts index 9764ff1edd..3fdc19d52c 100644 --- a/webapp/packages/core-connections/src/ContainerResource.ts +++ b/webapp/packages/core-connections/src/ContainerResource.ts @@ -8,9 +8,9 @@ import { observable } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { ExecutorInterrupter } from '@cloudbeaver/core-executor'; -import { EPermission, SessionPermissionsResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedDataResource, @@ -61,7 +61,7 @@ string constructor( private readonly graphQLService: GraphQLService, private readonly connectionInfoResource: ConnectionInfoResource, - permissionsResource: SessionPermissionsResource, + appAuthService: AppAuthService, ) { super(new Map()); @@ -75,7 +75,7 @@ string dependencies: observable([]), })); - permissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); this.preloadResource(connectionInfoResource, () => ConnectionInfoActiveProjectKey); this.before(ExecutorInterrupter.interrupter(key => !connectionInfoResource.isConnected(key))); @@ -205,6 +205,18 @@ string && param.catalogId === second.catalogId ); } + + protected validateParam(param: ObjectContainerParams): boolean { + return ( + super.validateParam(param) + || ( + typeof param === 'object' + && typeof param.projectId === 'string' + && ['string'].includes(typeof param.connectionId) + && ['string', 'undefined'].includes(typeof param.catalogId) + ) + ); + } } function serializeKey(key: ObjectContainerParams): string { diff --git a/webapp/packages/core-connections/src/DBDriverResource.ts b/webapp/packages/core-connections/src/DBDriverResource.ts index f951e0daba..cf42eec8ce 100644 --- a/webapp/packages/core-connections/src/DBDriverResource.ts +++ b/webapp/packages/core-connections/src/DBDriverResource.ts @@ -8,8 +8,9 @@ import { computed, makeObservable, runInAction } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, SessionPermissionsResource, ServerConfigResource } from '@cloudbeaver/core-root'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -34,10 +35,10 @@ export class DBDriverResource extends CachedMapResource this.markOutdated()); @@ -97,4 +98,11 @@ export class DBDriverResource extends CachedMapResource (Object.assign(oldDriver ?? {}, drivers[i])))); } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts b/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts index 3e05f5a067..6e9e474cbc 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts +++ b/webapp/packages/core-connections/src/DatabaseAuthModelsResource.ts @@ -11,7 +11,8 @@ import { DatabaseAuthModel, GraphQLService, CachedMapResource, - resourceKeyList + resourceKeyList, + ResourceKey } from '@cloudbeaver/core-sdk'; @injectable() @@ -27,4 +28,11 @@ export class DatabaseAuthModelsResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } \ No newline at end of file diff --git a/webapp/packages/core-connections/src/NetworkHandlerResource.ts b/webapp/packages/core-connections/src/NetworkHandlerResource.ts index d6d65e6adb..eb19153d12 100644 --- a/webapp/packages/core-connections/src/NetworkHandlerResource.ts +++ b/webapp/packages/core-connections/src/NetworkHandlerResource.ts @@ -13,7 +13,8 @@ import { GraphQLService, CachedMapResource, resourceKeyList, - NetworkHandlerConfigInput + NetworkHandlerConfigInput, + ResourceKey } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -21,7 +22,7 @@ export const SSH_TUNNEL_ID = 'ssh_tunnel'; @injectable() export class NetworkHandlerResource extends CachedMapResource { - private loadedKeyMetadata: MetadataMap; + private readonly loadedKeyMetadata: MetadataMap; constructor( private readonly graphQLService: GraphQLService, @@ -67,4 +68,11 @@ export class NetworkHandlerResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts index a5be5712c4..801739741c 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/DBObjectResource.ts @@ -98,6 +98,13 @@ export class DBObjectResource extends CachedMapResource { this.markOutdated(outdateKey); // } } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } function isDBObjectParentKey( diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeInfoResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeInfoResource.ts index b0d639d325..c035fd40cb 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeInfoResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeInfoResource.ts @@ -8,8 +8,8 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; -import { SessionPermissionsResource, EPermission } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -39,7 +39,7 @@ export class NavNodeInfoResource extends CachedMapResource { protected metadata: MetadataMap; constructor( private readonly graphQLService: GraphQLService, - permissionsResource: SessionPermissionsResource, + appAuthService: AppAuthService, ) { super(); @@ -58,7 +58,7 @@ export class NavNodeInfoResource extends CachedMapResource { setParent: action, }); - permissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); } updateNode(key: string, node: NavNode): void; @@ -218,6 +218,13 @@ export class NavNodeInfoResource extends CachedMapResource { return navNode; }); } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } export function getNodeDisplayName(node: NavNode): string { diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts index c0cdfc0dfe..5e729ea92e 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavTreeResource.ts @@ -9,11 +9,11 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { CoreSettingsService } from '@cloudbeaver/core-app'; -import { UserInfoResource } from '@cloudbeaver/core-authentication'; +import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; import { ProjectInfoResource } from '@cloudbeaver/core-projects'; -import { EPermission, SessionPermissionsResource, SessionDataResource } from '@cloudbeaver/core-root'; +import { SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -78,7 +78,7 @@ export class NavTreeResource extends CachedMapResource { private readonly sessionDataResource: SessionDataResource, private readonly userInfoResource: UserInfoResource, private readonly projectInfoResource: ProjectInfoResource, - permissionsResource: SessionPermissionsResource, + appAuthService: AppAuthService, ) { super(); @@ -105,7 +105,7 @@ export class NavTreeResource extends CachedMapResource { dependencies: observable([]), })); - permissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); // this.preloadResource(connectionInfo, () => CachedMapAllKey); this.onNodeRefresh = new Executor(null, (a, b) => a === b); @@ -524,4 +524,11 @@ export class NavTreeResource extends CachedMapResource { return { navNodeChildren, navNodeInfo, parentPath }; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-projects/src/ProjectInfoResource.ts b/webapp/packages/core-projects/src/ProjectInfoResource.ts index 8c56734015..0c9321d696 100644 --- a/webapp/packages/core-projects/src/ProjectInfoResource.ts +++ b/webapp/packages/core-projects/src/ProjectInfoResource.ts @@ -8,10 +8,9 @@ import { runInAction } from 'mobx'; -import { UserInfoResource } from '@cloudbeaver/core-authentication'; +import { AppAuthService, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; import { SharedProjectsResource } from '@cloudbeaver/core-resource-manager'; -import { EPermission, SessionPermissionsResource } from '@cloudbeaver/core-root'; import { GraphQLService, ProjectInfo as SchemaProjectInfo, CachedMapResource, CachedMapAllKey, ResourceKey, ResourceKeyUtils, resourceKeyList } from '@cloudbeaver/core-sdk'; export type ProjectInfo = SchemaProjectInfo; @@ -22,13 +21,13 @@ export class ProjectInfoResource extends CachedMapResource private readonly graphQLService: GraphQLService, private readonly sharedProjectsResource: SharedProjectsResource, private readonly userInfoResource: UserInfoResource, - sessionPermissionsResource: SessionPermissionsResource + appAuthService: AppAuthService, ) { super(new Map(), []); - this.sync(this.userInfoResource); + this.sync(this.userInfoResource, () => {}, () => CachedMapAllKey); this.sharedProjectsResource.connect(this); - sessionPermissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); this.sharedProjectsResource.onDataOutdated.addHandler(this.markOutdated.bind(this)); this.sharedProjectsResource.onItemAdd.addHandler(() => this.markOutdated()); this.sharedProjectsResource.onItemDelete.addHandler(() => this.markOutdated()); @@ -56,6 +55,13 @@ export class ProjectInfoResource extends CachedMapResource return this.data; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } export function projectInfoSortByName(a: ProjectInfo, b: ProjectInfo) { diff --git a/webapp/packages/core-resource-manager/src/ProjectPermissionsResource.ts b/webapp/packages/core-resource-manager/src/ProjectPermissionsResource.ts index 7ed66f2c42..d6d125eb41 100644 --- a/webapp/packages/core-resource-manager/src/ProjectPermissionsResource.ts +++ b/webapp/packages/core-resource-manager/src/ProjectPermissionsResource.ts @@ -12,7 +12,7 @@ import { ServerConfigResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk'; @injectable() -export class ProjectPermissionsResource extends CachedDataResource { +export class ProjectPermissionsResource extends CachedDataResource { constructor( private readonly graphQLService: GraphQLService, serverConfigResource: ServerConfigResource diff --git a/webapp/packages/core-resource-manager/src/ResourceManagerResource.ts b/webapp/packages/core-resource-manager/src/ResourceManagerResource.ts index e65f440cd6..c729dfac18 100644 --- a/webapp/packages/core-resource-manager/src/ResourceManagerResource.ts +++ b/webapp/packages/core-resource-manager/src/ResourceManagerResource.ts @@ -498,6 +498,18 @@ export class ResourceManagerResource includeProperties: false, }; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || ( + typeof param === 'object' && !isResourceKeyList(param) + && typeof param.projectId === 'string' + && ['string', 'undefined'].includes(typeof param.path) + && ['string', 'undefined'].includes(typeof param.name) + ) + ); + } } export function createParentResourceKey(key: IResourceManagerParams): IResourceManagerParams { diff --git a/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts b/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts index db9c33de8b..18a2f43865 100644 --- a/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts +++ b/webapp/packages/core-resource-manager/src/SharedProjectsResource.ts @@ -149,6 +149,13 @@ export class SharedProjectsResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } export function isEqualSharedProjectGrantInfo(a: AdminObjectGrantInfo, b: AdminObjectGrantInfo): boolean { diff --git a/webapp/packages/core-root/src/FeaturesResource.ts b/webapp/packages/core-root/src/FeaturesResource.ts index ace9cf3466..c6157bc4ba 100644 --- a/webapp/packages/core-root/src/FeaturesResource.ts +++ b/webapp/packages/core-root/src/FeaturesResource.ts @@ -18,7 +18,7 @@ import { ServerConfigResource } from './ServerConfigResource'; export type ApplicationFeature = WebFeatureSet; @injectable() -export class FeaturesResource extends CachedDataResource { +export class FeaturesResource extends CachedDataResource { constructor( private readonly graphQLService: GraphQLService, serverConfigResource: ServerConfigResource diff --git a/webapp/packages/core-root/src/PermissionsService.ts b/webapp/packages/core-root/src/PermissionsService.ts index c880809184..d0c309c878 100644 --- a/webapp/packages/core-root/src/PermissionsService.ts +++ b/webapp/packages/core-root/src/PermissionsService.ts @@ -11,7 +11,6 @@ import { injectable } from '@cloudbeaver/core-di'; import { SessionPermissionsResource } from './SessionPermissionsResource'; export enum EPermission { - public = 'public' } @injectable() diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 251e3ac586..bc4ac39752 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -73,13 +73,21 @@ export class ServerConfigResource extends CachedDataResource>( + async refresh = []>( param: TParam, context?: T ): Promise> { @@ -84,7 +84,7 @@ export abstract class CachedDataResource< return this.data as CachedResourceValueIncludes; } - async load>( + async load = []>( param: TParam, context?: T ): Promise> { @@ -92,6 +92,14 @@ export abstract class CachedDataResource< await this.loadData(param, false, context); return this.data as CachedResourceValueIncludes; } + + + protected validateParam(param: TParam): boolean { + return ( + super.validateParam(param) + || typeof param === 'undefined' + ); + } } export function getCachedDataResourceLoaderState< diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index dec0f6f121..d594f37b70 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -13,7 +13,7 @@ import { ILoadableState, isArraysEqual, isContainsException, MetadataMap, uuid } import { CachedResource, CachedResourceKey, CachedResourceParamKey, ICachedResourceMetadata } from './CachedResource'; import type { CachedResourceIncludeArgs, CachedResourceValueIncludes } from './CachedResourceIncludes'; -import { ResourceKey, resourceKeyList, ResourceKeyList, ResourceKeyUtils } from './ResourceKeyList'; +import { isResourceKeyList, ResourceKey, resourceKeyList, ResourceKeyList, ResourceKeyUtils } from './ResourceKeyList'; export type CachedMapResourceKey = CachedResourceKey; export type CachedMapResourceValue = TResource extends CachedResource, any, any, any, any> @@ -40,8 +40,8 @@ export type CachedMapResourceLoader< ? Array> : CachedResourceValueIncludes; +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ICachedMapResourceMetadata extends ICachedResourceMetadata { - includes: string[]; } export const CachedMapAllKey = resourceKeyList([Symbol('@cached-map-resource/all')], 'all'); @@ -82,7 +82,7 @@ export abstract class CachedMapResource< outdated: true, loading: false, exception: null, - includes: observable([...this.defaultIncludes]), + includes: observable([...this.defaultIncludes as any]), dependencies: observable([]), ...this.populateMetadata(key, metadata), }, undefined, { deep: false })); @@ -384,19 +384,19 @@ export abstract class CachedMapResource< } } - async refresh>( + async refresh = []>( key: TKey, includes?: T ): Promise>; - async refresh>( + async refresh = []>( key: ResourceKeyList, includes?: T ): Promise>>; - async refresh>( + async refresh = []>( key: ResourceKey, includes?: T ): Promise> | CachedResourceValueIncludes>; - async refresh>( + async refresh = []>( key: ResourceKey, includes?: T ): Promise> | CachedResourceValueIncludes> { @@ -405,19 +405,19 @@ export abstract class CachedMapResource< return this.get(key) as Array> | CachedResourceValueIncludes; } - async load>( + async load = []>( key: TKey, includes?: T ): Promise>; - async load>( + async load = []>( key: ResourceKeyList, includes?: T ): Promise>>; - async load>( + async load = []>( key: ResourceKey, includes?: T ): Promise> | CachedResourceValueIncludes>; - async load>( + async load = []>( key: ResourceKey, includes?: T ): Promise> | CachedResourceValueIncludes> { @@ -622,6 +622,13 @@ export abstract class CachedMapResource< this.onDataOutdated.execute(key); } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || isResourceKeyList(param) + ); + } } export function getCachedMapResourceLoaderState< diff --git a/webapp/packages/core-sdk/src/Resource/CachedResource.ts b/webapp/packages/core-sdk/src/Resource/CachedResource.ts index 65f3cfdc3c..54ce91783c 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResource.ts @@ -6,12 +6,14 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable, action, computed } from 'mobx'; +import { observable, makeObservable, action, computed, toJS } from 'mobx'; import { Dependency } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor, IExecutorHandler, ISyncExecutor, SyncExecutor, TaskScheduler } from '@cloudbeaver/core-executor'; import { MetadataMap, uuid } from '@cloudbeaver/core-utils'; +import { isResourceKeyList } from './ResourceKeyList'; + export interface ICachedResourceMetadata { outdated: boolean; loading: boolean; @@ -254,7 +256,7 @@ export abstract class CachedResource< param = map(param) as any as TParam; } - await resource.load(param as any as T, []); + await resource.load(param as any as T); } finally { if (this.logActivity) { console.groupEnd(); @@ -475,6 +477,16 @@ export abstract class CachedResource< } transformParam(param: TParam): TParam { + if (!this.validateParam(param)) { + let paramString = JSON.stringify(toJS(param)); + + if (typeof param === 'symbol') { + paramString = param.toString(); + } else if (isResourceKeyList(param)) { + paramString = param.toString(); + } + console.warn(this.getActionPrefixedName(`wrong param "${paramString}"`)); + } let deep = 0; // eslint-disable-next-line no-labels transform: @@ -538,6 +550,10 @@ export abstract class CachedResource< }, {}); } + protected validateParam(param: TParam): boolean { + return param === CachedResourceParamKey; + } + protected resetIncludes(): void { for (const metadata of this.metadata.values()) { metadata.includes = observable([...this.defaultIncludes]); diff --git a/webapp/packages/core-sdk/src/Resource/CachedResourceIncludes.ts b/webapp/packages/core-sdk/src/Resource/CachedResourceIncludes.ts index 6ef859aac9..ea5ff738a1 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResourceIncludes.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResourceIncludes.ts @@ -19,23 +19,26 @@ export type CachedResourceIncludeFlags = { export type CachedResourceIncludeList = Array>; export type CachedResourceIncludeToKey = TKey extends ReadonlyArray<`include${infer T}` | `customInclude${Capitalize}`> ? Uncapitalize : unknown; -export type CachedResourceIncludeArgs = TArguments extends Record - ? string[] - : ( - Array< - Exclude< - keyof CachedResourceIncludeFlags, undefined | null>, TArguments>, - number | symbol - > - > - ); +export type CachedResourceIncludeArgs = ( + TArguments extends Record + ? string[] + : ( + Array< + Exclude< + keyof CachedResourceIncludeFlags, undefined | null>, TArguments>, + number | symbol + > + > + ) +) | []; -export type ApplyIncludes = TValue -& ({ - [P in Extract, keyof TValue>]-?: Required[P] extends undefined - ? TValue[P] - : NonNullable; -}); +export type ApplyIncludes = TValue extends null | undefined + ? TValue + : TValue & ({ + [P in Extract, keyof TValue>]-?: Required[P] extends undefined + ? TValue[P] + : NonNullable; + }); export type CachedResourceValueElementIncludes = TValue extends any ? ( @@ -49,10 +52,14 @@ export type CachedResourceValueElementIncludes = TValue extends a export type CachedResourceValueIncludes = TValue extends any ? ( - TValue extends Array - ? TElement extends Record - ? Array> - : ApplyIncludes - : ApplyIncludes + TKeys extends [] + ? TValue + : ( + TValue extends Array + ? TElement extends Record + ? Array> + : ApplyIncludes + : ApplyIncludes + ) ) : undefined; diff --git a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts index e478459178..527cd8813f 100644 --- a/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts +++ b/webapp/packages/core-sdk/src/Resource/ResourceKeyList.ts @@ -31,6 +31,10 @@ export class ResourceKeyList { return this.list.some(current => isEqual(current, key)); } + + toString(): string { + return `ResourceKeyList(${this.list.join()})${this.mark !== undefined ? '@' + this.mark : ''}`; + } } interface MapFnc { diff --git a/webapp/packages/core-ui/src/SideBarPanel/SideBarPanelService.ts b/webapp/packages/core-ui/src/SideBarPanel/SideBarPanelService.ts index bbf35a886f..caa17e8038 100644 --- a/webapp/packages/core-ui/src/SideBarPanel/SideBarPanelService.ts +++ b/webapp/packages/core-ui/src/SideBarPanel/SideBarPanelService.ts @@ -15,6 +15,6 @@ export class SideBarPanelService { readonly tabsContainer: TabsContainer; constructor() { - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('Right Side Bar'); } } \ No newline at end of file diff --git a/webapp/packages/core-ui/src/Tabs/TabList.tsx b/webapp/packages/core-ui/src/Tabs/TabList.tsx index 3d58c2694c..f611b12850 100644 --- a/webapp/packages/core-ui/src/Tabs/TabList.tsx +++ b/webapp/packages/core-ui/src/Tabs/TabList.tsx @@ -17,6 +17,7 @@ import { TabDefault } from './Tab/TabDefault'; import { TabsContext } from './TabsContext'; interface Props extends Omit { + 'aria-label'?: string; style?: ComponentStyle; childrenFirst?: boolean; } @@ -36,7 +37,7 @@ export const TabList = observer>(function TabList if (state.container) { const displayed = state.container.getDisplayed(state.props); return ( - + {childrenFirst && children} {displayed.map(generateTabElement( (tabInfo, key) => ( @@ -48,6 +49,7 @@ export const TabList = observer>(function TabList component={tabInfo.tab?.()} {...state.props} style={style} + aria-label={tabInfo.name} disabled={props.disabled || tabInfo.isDisabled?.(tabInfo.key, state.props)} onOpen={tabInfo.onOpen} onClose={tabInfo.onClose} diff --git a/webapp/packages/core-ui/src/Tabs/TabsContainer/ITabsContainer.ts b/webapp/packages/core-ui/src/Tabs/TabsContainer/ITabsContainer.ts index 56e1efa4c7..69103a02b0 100644 --- a/webapp/packages/core-ui/src/Tabs/TabsContainer/ITabsContainer.ts +++ b/webapp/packages/core-ui/src/Tabs/TabsContainer/ITabsContainer.ts @@ -48,6 +48,7 @@ export interface ITabInfo< } export interface ITabsContainer = never> { + readonly areaLabel: string; readonly tabInfoList: Array>; readonly selectedId: string | null; has: (tabId: string) => boolean; diff --git a/webapp/packages/core-ui/src/Tabs/TabsContainer/TabsContainer.ts b/webapp/packages/core-ui/src/Tabs/TabsContainer/TabsContainer.ts index e333451f06..e76c7f8084 100644 --- a/webapp/packages/core-ui/src/Tabs/TabsContainer/TabsContainer.ts +++ b/webapp/packages/core-ui/src/Tabs/TabsContainer/TabsContainer.ts @@ -14,6 +14,7 @@ import type { ITabInfo, ITabInfoOptions, ITabsContainer } from './ITabsContainer export class TabsContainer = never> implements ITabsContainer { + readonly areaLabel: string; readonly tabInfoMap: Map>; get tabInfoList(): Array> { @@ -27,9 +28,10 @@ implements ITabsContainer { private currentTabId: string | null; - constructor() { + constructor(areaLabel: string) { this.tabInfoMap = new Map(); this.currentTabId = null; + this.areaLabel = areaLabel; makeObservable, 'currentTabId'>(this, { tabInfoMap: observable.shallow, diff --git a/webapp/packages/core-ui/src/Tabs/TabsState.tsx b/webapp/packages/core-ui/src/Tabs/TabsState.tsx index 960cfae9ae..8b4b3cd591 100644 --- a/webapp/packages/core-ui/src/Tabs/TabsState.tsx +++ b/webapp/packages/core-ui/src/Tabs/TabsState.tsx @@ -8,11 +8,11 @@ import { action, observable } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTabState } from 'reakit/Tab'; import { useExecutor, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks'; -import { Executor, ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; +import { Executor, ExecutorInterrupter } from '@cloudbeaver/core-executor'; import { MetadataMap, MetadataValueGetter } from '@cloudbeaver/core-utils'; import type { ITabData, ITabsContainer } from './TabsContainer/ITabsContainer'; @@ -52,7 +52,7 @@ export const TabsState = observer(function TabsState>({ onClose, ...rest }: Props): React.ReactElement | null { - const props = rest as any as T; + const props = useMemo(() => rest as any as T, [...Object.values(rest)]); let displayed: string[] = []; if (container) { diff --git a/webapp/packages/core-version/src/VersionResource.ts b/webapp/packages/core-version/src/VersionResource.ts index 684078858f..90dc42af60 100644 --- a/webapp/packages/core-version/src/VersionResource.ts +++ b/webapp/packages/core-version/src/VersionResource.ts @@ -10,7 +10,7 @@ import { computed, makeObservable, observable, runInAction } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { ServerConfigResource } from '@cloudbeaver/core-root'; -import { CachedMapAllKey, CachedMapResource } from '@cloudbeaver/core-sdk'; +import { CachedMapAllKey, CachedMapResource, ResourceKey } from '@cloudbeaver/core-sdk'; export interface IVersion { number: string; @@ -85,4 +85,11 @@ export class VersionResource extends CachedMapResource { return this.data; } + + protected validateParam(param: ResourceKey): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts b/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts index f96e41842d..cbae6ddfcb 100644 --- a/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts +++ b/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts @@ -31,7 +31,9 @@ export function useDynamicDataContext( state.dynamic.flush(); }); - capture(state.dynamic); + useEffect(() => { + capture(state.dynamic); + }); useEffect(() => () => state.dynamic.flush(), []); } diff --git a/webapp/packages/plugin-administration/src/AdministrationMenu/AdministrationMenu.tsx b/webapp/packages/plugin-administration/src/AdministrationMenu/AdministrationMenu.tsx index 99a4970b0d..c4c3eff9df 100644 --- a/webapp/packages/plugin-administration/src/AdministrationMenu/AdministrationMenu.tsx +++ b/webapp/packages/plugin-administration/src/AdministrationMenu/AdministrationMenu.tsx @@ -9,8 +9,8 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { usePermission } from '@cloudbeaver/core-blocks'; -import { EPermission } from '@cloudbeaver/core-root'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; +import { useService } from '@cloudbeaver/core-di'; import { MenuBar } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; import { MENU_BAR_DISABLE_EFFECT_STYLES, MENU_BAR_ITEM_STYLES, MENU_BAR_STYLES, topMenuStyles } from '@cloudbeaver/plugin-top-app-bar'; @@ -19,9 +19,9 @@ import { MENU_APP_ADMINISTRATION_ACTIONS } from './MENU_APP_ADMINISTRATION_ACTIO export const AdministrationMenu = observer(function AdministrationMenu() { const menu = useMenu({ menu: MENU_APP_ADMINISTRATION_ACTIONS }); - const isEnabled = usePermission(EPermission.public); + const { authenticated } = useService(AppAuthService); - if (!isEnabled) { + if (!authenticated) { return null; } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationFeaturesForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationFeaturesForm.tsx index 139c1ba5e2..98a0757042 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationFeaturesForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationFeaturesForm.tsx @@ -14,7 +14,6 @@ import { AdministrationSettingsService } from '@cloudbeaver/core-administration' import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, Loader, PlaceholderComponent, Switch, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { FeaturesResource } from '@cloudbeaver/core-root'; -import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration'; export const ServerConfigurationFeaturesForm: PlaceholderComponent = observer(function ServerConfigurationFeaturesForm({ @@ -22,7 +21,7 @@ export const ServerConfigurationFeaturesForm: PlaceholderComponent = (data, contexts) => { + private readonly showSubmittingStatusMessage: IExecutorHandler = (data, contexts) => { const status = contexts.getContext(this.configurationStatusContext); if (!status.saved) { @@ -116,7 +116,7 @@ export class AuthConfigurationFormService { } }; - private ensureValidation: IExecutorHandler = (data, contexts) => { + private readonly ensureValidation: IExecutorHandler = (data, contexts) => { const validation = contexts.getContext(this.configurationValidationContext); if (!validation.valid) { diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts index d56fd863cb..876c0564cc 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/ServerConfigurationAuthenticationBootstrap.ts @@ -6,6 +6,7 @@ * you may not use this file except in compliance with the License. */ +import { AdministrationScreenService } from '@cloudbeaver/core-administration'; import { AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -16,6 +17,7 @@ import { ILoadConfigData, IServerConfigSaveData, ServerConfigurationService, ser @injectable() export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { constructor( + private readonly administrationScreenService: AdministrationScreenService, private readonly serverConfigurationService: ServerConfigurationService, private readonly authProvidersResource: AuthProvidersResource, private readonly serverConfigResource: ServerConfigResource, @@ -43,7 +45,7 @@ export class ServerConfigurationAuthenticationBootstrap extends Bootstrap { return; } - if (config.configurationMode) { + if (this.administrationScreenService.isConfigurationMode) { await this.authProvidersResource.loadAll(); if (this.authProvidersResource.has(AUTH_PROVIDER_LOCAL_ID)) { data.state.serverConfig.adminName = 'cbadmin'; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormService.ts index efba93b3bc..edfd89364d 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormService.ts @@ -46,7 +46,7 @@ export class TeamFormService { constructor( private readonly notificationService: NotificationService, ) { - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('Team settings'); this.actionsContainer = new PlaceholderContainer(); this.configureTask = new ExecutorHandlersCollection(); this.fillConfigTask = new ExecutorHandlersCollection(); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts index 28c949f95c..affbe27b2c 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UserForm/UserFormService.ts @@ -33,7 +33,7 @@ export class UserFormService { readonly tabsContainer: TabsContainer; constructor() { - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('User settings'); this.onFormInit = new Executor(); } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx index fc19fdced1..793123a8c3 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx @@ -59,7 +59,7 @@ export const UsersAdministration: AdministrationItemContentComponent = observer( return styled(style)( - + {translate('authentication_administration_item_users')} {translate('administration_teams_tab_title')} {/* this.requireAuthentication()); this.administrationScreenService.ensurePermissions.addHandler(async () => { - const userInfo = await this.userInfoResource.load(undefined, []); + const userInfo = await this.userInfoResource.load(); if (userInfo) { return; } @@ -224,7 +224,7 @@ export class AuthenticationService extends Bootstrap { } await this.authProvidersResource.loadAll(); - await this.userInfoResource.load(undefined, []); + await this.userInfoResource.load(); if (!this.authProvidersResource.has(data.providerId)) { return; diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts index 353fe70736..38ba7e3837 100644 --- a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts @@ -6,10 +6,11 @@ * you may not use this file except in compliance with the License. */ +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects'; -import { PermissionsService, EPermission } from '@cloudbeaver/core-root'; +import { PermissionsService } from '@cloudbeaver/core-root'; import { CachedMapAllKey, getCachedDataResourceLoaderState, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; import { MenuService, ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_LOADABLE_STATE } from '@cloudbeaver/core-view'; import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; @@ -22,6 +23,7 @@ import { TemplateConnectionsService } from './TemplateConnectionsService'; @injectable() export class TemplateConnectionPluginBootstrap extends Bootstrap { constructor( + private readonly appAuthService: AppAuthService, private readonly menuService: MenuService, private readonly actionService: ActionService, private readonly projectInfoResource: ProjectInfoResource, @@ -49,7 +51,7 @@ export class TemplateConnectionPluginBootstrap extends Bootstrap { ACTION_CONNECTION_TEMPLATE, ].includes(action), isHidden: () => ( - !this.permissionsService.has(EPermission.public) + !this.appAuthService.authenticated || !this.projectsService.userProject?.canEditDataSources || !this.templateConnectionsService.projectTemplates.length ), @@ -59,6 +61,7 @@ export class TemplateConnectionPluginBootstrap extends Bootstrap { return state.getState( action.id, () => [ + ...this.appAuthService.loaders, getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey), getCachedDataResourceLoaderState(this.templateConnectionsResource, undefined, undefined), ] diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts index da2e43a778..0d213f9703 100644 --- a/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts +++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionsResource.ts @@ -6,9 +6,10 @@ * you may not use this file except in compliance with the License. */ +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { Connection, ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, SessionDataResource, SessionPermissionsResource } from '@cloudbeaver/core-root'; +import { SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedDataResource, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; @injectable() @@ -17,15 +18,13 @@ export class TemplateConnectionsResource extends CachedDataResource { if (connection.template) { diff --git a/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts b/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts index 41fb925d0b..74a1e09218 100644 --- a/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts +++ b/webapp/packages/plugin-connections-administration/src/Administration/Connections/CreateConnectionService.ts @@ -41,7 +41,7 @@ export class CreateConnectionService { private readonly projectInfoResource: ProjectInfoResource ) { this.data = null; - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('Connection Creation mode'); this.setConnectionTemplate = this.setConnectionTemplate.bind(this); this.clearConnectionTemplate = this.clearConnectionTemplate.bind(this); diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts index e7b2b35191..13ffcf6313 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/ConnectionFormService.ts @@ -52,7 +52,7 @@ export class ConnectionFormService { private readonly notificationService: NotificationService, private readonly commonDialogService: CommonDialogService, ) { - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('Connection settings'); this.actionsContainer = new PlaceholderContainer(); this.configureTask = new ExecutorHandlersCollection(); this.fillConfigTask = new ExecutorHandlersCollection(); diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index 7f4750db9e..5cd58dce13 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -344,7 +344,7 @@ export class ConnectionOptionsTabService extends Bootstrap { const providerId = authModel.requiredAuth ?? data.info?.requiredAuth ?? AUTH_PROVIDER_LOCAL_ID; - await this.userInfoResource.load(undefined, []); + await this.userInfoResource.load(); if (!this.userInfoResource.hasToken(providerId)) { const provider = await this.authProvidersResource.load(providerId); diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/connectionFormConfigureContext.ts b/webapp/packages/plugin-connections/src/ConnectionForm/connectionFormConfigureContext.ts index 25a5549708..8bf6cb2f10 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/connectionFormConfigureContext.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/connectionFormConfigureContext.ts @@ -35,8 +35,8 @@ export function connectionFormConfigureContext( connectionIncludes: [], include(...includes) { for (const include of includes) { - if (!this.connectionIncludes.includes(include)) { - this.connectionIncludes.push(include); + if (!this.connectionIncludes.includes(include as never)) { + this.connectionIncludes.push(include as never); } } }, diff --git a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts index d06a37af33..fd3e853675 100644 --- a/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts +++ b/webapp/packages/plugin-data-export/src/DataTransferProcessorsResource.ts @@ -10,7 +10,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { GraphQLService, CachedDataResource, DataTransferProcessorInfo } from '@cloudbeaver/core-sdk'; @injectable() -export class DataTransferProcessorsResource extends CachedDataResource, void> { +export class DataTransferProcessorsResource extends CachedDataResource> { constructor( private readonly graphQLService: GraphQLService ) { diff --git a/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx b/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx index 12323e01a5..feb9163723 100644 --- a/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx +++ b/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx @@ -116,7 +116,7 @@ export const ProcessorConfigureDialog = observer(function ProcessorConfig > {!processor.isBinary ? ( - + {translate('data_transfer_format_settings')} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TablePresentationBar/TablePresentationBar.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TablePresentationBar/TablePresentationBar.tsx index 6d1b507494..2ef55b38bd 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TablePresentationBar/TablePresentationBar.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TablePresentationBar/TablePresentationBar.tsx @@ -89,7 +89,7 @@ export const TablePresentationBar = observer(function TablePresentationBa return styled(style)( - + {presentations.map(presentation => ( , IDataValuePanelOptions>; constructor() { - this.tabs = new TabsContainer(); + this.tabs = new TabsContainer('Value Panel'); } get(tabId: string): ITabInfo, IDataValuePanelOptions> | undefined { diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts index 8fd14dcaa5..51d8ffd505 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentationService.ts @@ -6,15 +6,15 @@ * you may not use this file except in compliance with the License. */ -import { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui'; import { injectable } from '@cloudbeaver/core-di'; +import { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui'; @injectable() export class TextValuePresentationService { readonly tabs: TabsContainer; constructor() { - this.tabs = new TabsContainer(); + this.tabs = new TabsContainer('Value presentation'); } get(tabId: string): ITabInfo | undefined { diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts index 881f5160d5..8450b3e4e1 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSchemaManagerBootstrap.ts @@ -6,10 +6,10 @@ * you may not use this file except in compliance with the License. */ +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { compareConnectionsInfo, ConnectionInfoResource, ConnectionsManagerService, ContainerResource, createConnectionParam, serializeConnectionParam } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { EObjectFeature, NodeManagerUtils } from '@cloudbeaver/core-navigation-tree'; -import { EPermission, PermissionsService } from '@cloudbeaver/core-root'; import { getCachedDataResourceLoaderState } from '@cloudbeaver/core-sdk'; import { OptionsPanelService } from '@cloudbeaver/core-ui'; import { DATA_CONTEXT_LOADABLE_STATE, DATA_CONTEXT_MENU, MenuBaseItem, menuExtractItems, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view'; @@ -36,7 +36,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { private readonly connectionSchemaManagerService: ConnectionSchemaManagerService, private readonly connectionsManagerService: ConnectionsManagerService, private readonly optionsPanelService: OptionsPanelService, - private readonly permissionsService: PermissionsService, + private readonly appAuthService: AppAuthService, private readonly containerResource: ContainerResource, private readonly menuService: MenuService ) { @@ -53,7 +53,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { id: 'connection-selector-base', isApplicable: context => context.hasValue(DATA_CONTEXT_MENU, MENU_CONNECTION_SELECTOR), isLoading: () => this.connectionSelectorLoading, - isHidden: () => this.isHidden(), + isHidden: () => this.isHidden() || !this.appAuthService.authenticated, isDisabled: () => ( !this.connectionSchemaManagerService.isConnectionChangeable || this.connectionSelectorLoading @@ -80,10 +80,13 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { return state.getState( menu.id, - () => getCachedDataResourceLoaderState(this.containerResource, { - ...this.connectionSchemaManagerService.activeConnectionKey!, - catalogId: this.connectionSchemaManagerService.activeObjectCatalogId, - }, undefined) + () => [ + ...this.appAuthService.loaders, + getCachedDataResourceLoaderState(this.containerResource, { + ...this.connectionSchemaManagerService.activeConnectionKey!, + catalogId: this.connectionSchemaManagerService.activeObjectCatalogId, + }, undefined), + ] ); }, }); @@ -166,6 +169,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { ), isHidden: () => ( this.isHidden() + || !this.appAuthService.authenticated || !this.connectionSchemaManagerService.objectContainerList || ( this.connectionSchemaManagerService.currentObjectSchemaId === undefined @@ -178,6 +182,18 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { && this.connectionSchemaManagerService.objectContainerList.catalogList.length === 0 ) ), + getLoader: (context, menu) => { + if (this.isHidden()) { + return []; + } + + const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + + return state.getState( + menu.id, + () => this.appAuthService.loaders + ); + }, getInfo: (context, menu) => { const connectionSchemaManagerService = this.connectionSchemaManagerService; @@ -375,7 +391,6 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap { !this.connectionSchemaManagerService.isConnectionChangeable && !this.connectionSchemaManagerService.currentConnectionKey ) - || !this.permissionsService.has(EPermission.public) ); } diff --git a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx index 3bb0dace31..168ca4d924 100644 --- a/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx +++ b/webapp/packages/plugin-datasource-context-switch/src/ConnectionSchemaManager/ConnectionSelector/ConnectionIcon.tsx @@ -37,7 +37,7 @@ interface Props extends IConnectionSelectorExtraProps { className?: string; } -export const ConnectionIcon: React.FC = observer(function ConnectionInfo({ +export const ConnectionIcon: React.FC = observer(function ConnectionIcon({ connectionKey, small, style, @@ -46,13 +46,13 @@ export const ConnectionIcon: React.FC = observer(function ConnectionInfo( const styles = useStyles(style, connectionIconStyle); const connection = useResource( - ConnectionInfo, + ConnectionIcon, ConnectionInfoResource, connectionKey ?? null ); const driverId = connection.data?.driverId; - const driver = useResource(ConnectionInfo, DBDriverResource, driverId!, { + const driver = useResource(ConnectionIcon, DBDriverResource, driverId!, { active: driverId !== undefined, }); diff --git a/webapp/packages/plugin-log-viewer/src/SessionLogsResource.ts b/webapp/packages/plugin-log-viewer/src/SessionLogsResource.ts index 4d42122a62..2ff252b136 100644 --- a/webapp/packages/plugin-log-viewer/src/SessionLogsResource.ts +++ b/webapp/packages/plugin-log-viewer/src/SessionLogsResource.ts @@ -9,8 +9,9 @@ import { runInAction } from 'mobx'; import { CoreSettingsService } from '@cloudbeaver/core-app'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; import { injectable } from '@cloudbeaver/core-di'; -import { EPermission, ServerEventId, SessionDataResource, SessionPermissionsResource } from '@cloudbeaver/core-root'; +import { ServerEventId, SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedDataResource, LogEntry } from '@cloudbeaver/core-sdk'; import { uuid } from '@cloudbeaver/core-utils'; @@ -29,7 +30,7 @@ export class SessionLogsResource extends CachedDataResource { private readonly coreSettingsService: CoreSettingsService, private readonly logViewerSettingsService: LogViewerSettingsService, sessionDataResource: SessionDataResource, - permissionsResource: SessionPermissionsResource, + appAuthService: AppAuthService, sessionLogsEventHandler: SessionLogsEventHandler, ) { super([]); @@ -38,7 +39,7 @@ export class SessionLogsResource extends CachedDataResource { this.clear(); }); - permissionsResource.require(this, EPermission.public); + appAuthService.requireAuthentication(this); sessionLogsEventHandler.onEvent(ServerEventId.CbSessionLogUpdated, () => { this.markOutdated(); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts index 1f9d399ec3..6f9890d0f0 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts @@ -132,6 +132,9 @@ export function useElementsTree(options: IOptions): IElementsTree { }))); options = useObjectRef(options); + options.renderers = useMemo(() => options.renderers || [], [...(options.renderers || [])]); + options.filters = useMemo(() => options.filters || [], [...(options.filters || [])]); + options.expandStateGetters = useMemo(() => options.expandStateGetters || [], [...(options.expandStateGetters || [])]); const state = options.localState || localTreeNodesState; const functionsRef = useObjectRef({ @@ -297,8 +300,6 @@ export function useElementsTree(options: IOptions): IElementsTree { ) ); - const renderers = useMemo(() => options.renderers || [], [options.renderers]); - const elementsTree = useObservableRef(() => ({ actions: new SyncExecutor(), state, @@ -548,7 +549,7 @@ export function useElementsTree(options: IOptions): IElementsTree { root: options.root, settings: options.settings, baseRoot: options.baseRoot, - renderers, + renderers: options.renderers, userData, }, ['isLoading', 'isLoaded']); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx index af3fc0d983..cb4076d315 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/NavigationTree.tsx @@ -10,11 +10,11 @@ import { observer } from 'mobx-react-lite'; import { useMemo } from 'react'; import styled, { css } from 'reshadow'; -import { Translate, usePermission, useUserData } from '@cloudbeaver/core-blocks'; +import { AppAuthService } from '@cloudbeaver/core-authentication'; +import { Translate, useUserData } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree'; import { ProjectsService } from '@cloudbeaver/core-projects'; -import { EPermission } from '@cloudbeaver/core-root'; import { CaptureView } from '@cloudbeaver/core-view'; import { NavNodeViewService } from '../NodesManager/NavNodeView/NavNodeViewService'; @@ -84,7 +84,7 @@ export const NavigationTree = observer(function NavigationTree() { const navNodeViewService = useService(NavNodeViewService); const root = ROOT_NODE_PATH; - const isEnabled = usePermission(EPermission.public); + const { authenticated } = useService(AppAuthService); const { handleOpen, handleSelect, handleSelectReset } = useNavigationTree(); const connectionGroupFilter = useMemo(() => navigationTreeConnectionGroupFilter( @@ -114,7 +114,7 @@ export const NavigationTree = observer(function NavigationTree() { const settingsElements = useMemo(() => ([ProjectsSettingsPlaceholderElement]), []); - if (!isEnabled) { + if (!authenticated) { return null; } diff --git a/webapp/packages/plugin-root/src/SessionExpireWarningDialog/SessionExpireWarningDialogService.ts b/webapp/packages/plugin-root/src/SessionExpireWarningDialog/SessionExpireWarningDialogService.ts index 4a19d27098..f334d7637d 100644 --- a/webapp/packages/plugin-root/src/SessionExpireWarningDialog/SessionExpireWarningDialogService.ts +++ b/webapp/packages/plugin-root/src/SessionExpireWarningDialog/SessionExpireWarningDialogService.ts @@ -44,7 +44,7 @@ export class SessionExpireWarningDialogService extends Bootstrap { private startSessionPolling() { const checkSessionStatus = async () => { if ( - !this.serverConfigResource.data?.anonymousAccessEnabled + !this.serverConfigResource.anonymousAccessEnabled && !this.userInfoResource.data && !this.serverConfigResource.configurationMode ) { diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts index 2f375cc7d7..c332091225 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts @@ -8,7 +8,7 @@ import { action, computed, makeObservable, observable, runInAction, toJS } from 'mobx'; -import { IConnectionExecutionContextInfo, NOT_INITIALIZED_CONTEXT_ID } from '@cloudbeaver/core-connections'; +import { ConnectionInfoResource, createConnectionParam, IConnectionExecutionContextInfo, NOT_INITIALIZED_CONTEXT_ID } from '@cloudbeaver/core-connections'; import { TaskScheduler } from '@cloudbeaver/core-executor'; import { IResourceManagerParams, isResourceManagerParamEqual, ResourceManagerResource } from '@cloudbeaver/core-resource-manager'; import { ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; @@ -70,6 +70,14 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { } get executionContext(): IConnectionExecutionContextInfo | undefined { + if ( + this.state.executionContext + && !this.connectionInfoResource.has(createConnectionParam( + this.state.executionContext.projectId, + this.state.executionContext.connectionId + ))) { + return undefined; + } return this.state.executionContext; } @@ -102,6 +110,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { private resourceUseKeyId: string | null; constructor( + private readonly connectionInfoResource: ConnectionInfoResource, private readonly resourceManagerResource: ResourceManagerResource, state: IResourceSqlDataSourceState ) { diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts index e0568ecf4d..396aa11eec 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts @@ -8,6 +8,7 @@ import { action, makeObservable, observable, untracked } from 'mobx'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -34,6 +35,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap { private readonly dataSourceStateState = new Map(); constructor( + private readonly connectionInfoResource: ConnectionInfoResource, private readonly networkStateService: NetworkStateService, private readonly sqlDataSourceService: SqlDataSourceService, private readonly commonDialogService: CommonDialogService, @@ -90,6 +92,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap { key: ResourceSqlDataSource.key, getDataSource: (editorId, options) => { const dataSource = new ResourceSqlDataSource( + this.connectionInfoResource, this.resourceManagerResource, this.createState( editorId, diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts index 6f3e37d6a2..878e46f971 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -175,13 +175,20 @@ export class SqlEditorTabService extends Bootstrap { } const { projectId, connectionId, defaultCatalog, defaultSchema } = dataSource.executionContext; + const connectionKey = createConnectionParam(projectId, connectionId); + + const connection = this.connectionInfoResource.get(connectionKey); + + if (!connection?.connected) { + return; + } let catalogData: ICatalogData | undefined; let schema: NavNodeInfoFragment | undefined; if (defaultCatalog) { catalogData = this.containerResource.getCatalogData( - createConnectionParam(projectId, connectionId), + connectionKey, defaultCatalog ); } @@ -196,12 +203,6 @@ export class SqlEditorTabService extends Bootstrap { nodeId = NodeManagerUtils.connectionIdToConnectionNodeId(connectionId); } - const connection = this.connectionInfoResource.getConnectionForNode(nodeId); - - if (connection?.connected === false) { - return; - } - const parents = this.navNodeInfoResource.getParents(nodeId); untracked(() => this.navNodeInfoResource.load(nodeId!)); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorModeService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorModeService.ts index da31b3d2a4..9fc0fd15b0 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorModeService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorModeService.ts @@ -22,6 +22,6 @@ export class SqlEditorModeService { readonly tabsContainer: TabsContainer; constructor() { - this.tabsContainer = new TabsContainer(); + this.tabsContainer = new TabsContainer('SQL Editor Mode'); } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlGenerators/SqlGeneratorsResource.ts b/webapp/packages/plugin-sql-editor/src/SqlGenerators/SqlGeneratorsResource.ts index a3cfe13a69..5e0448e054 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlGenerators/SqlGeneratorsResource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlGenerators/SqlGeneratorsResource.ts @@ -57,4 +57,11 @@ export class SqlGeneratorsResource extends CachedMapResource): boolean { + return ( + super.validateParam(param) + || typeof param === 'string' + ); + } } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx index 21172b6b96..a3558871d3 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx @@ -99,7 +99,7 @@ export const SqlResultTabs = observer(function SqlDataResult({ state, onT onChange={handleSelect} onClose={handleClose} > - + {orderedTabs.map(result => ( (function UserForm({ )} - + {localProvider && } {/* */}