From 9363dae717e3dd42b7c582f517e5aa123e68e920 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 31 Mar 2021 15:22:24 +0300 Subject: [PATCH] fix: CB-792 CB-730 CB-906 authentication & administration --- .../AdministrationScreenService.ts | 2 +- .../NavigationTreeContextMenuService.ts | 3 +- .../NavigationTabs/NavigationTabsService.ts | 4 ++ .../src/AuthInfoService.ts | 4 +- .../src/UserInfoResource.ts | 12 +++- .../src/ConnectionInfoResource.ts | 3 +- webapp/packages/core-connections/src/index.ts | 1 - webapp/packages/core-di/src/App.ts | 3 + webapp/packages/core-di/src/Dependency.ts | 13 ++++ webapp/packages/core-di/src/index.ts | 1 + .../src/CommonDialog/CommonDialogService.ts | 2 +- .../core-executor/src/IExecutionContext.ts | 2 +- .../src/TaskScheduler/TaskScheduler.ts | 33 ++++++---- .../src/ConnectionNavigatorViewSettings.ts | 0 .../core-root/src/ServerConfigResource.ts | 62 +++++++++++++++++-- .../packages/core-root/src/SessionResource.ts | 4 +- webapp/packages/core-root/src/index.ts | 1 + .../src/Resource/CachedDataResource.ts | 1 + .../core-sdk/src/Resource/CachedResource.ts | 43 +++++++------ .../src/queries/authentication/authLogin.gql | 3 +- .../queries/authentication/getActiveUser.gql | 1 + webapp/packages/core-sdk/src/sdk.ts | 14 ++++- .../ServerConfigurationNavigatorViewForm.tsx | 2 +- .../ServerConfigurationPage.tsx | 8 ++- .../ServerConfigurationService.ts | 2 +- .../src/AuthenticationService.ts | 13 ++-- .../src/Dialog/AuthDialog.tsx | 17 +++-- .../src/Dialog/AuthDialogController.ts | 6 +- .../src/Dialog/AuthDialogService.ts | 4 +- 29 files changed, 187 insertions(+), 77 deletions(-) create mode 100644 webapp/packages/core-di/src/Dependency.ts rename webapp/packages/{core-connections => core-root}/src/ConnectionNavigatorViewSettings.ts (100%) diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index 4d42f05129..20eae196eb 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -86,7 +86,7 @@ export class AdministrationScreenService { this.autoSaveService.withAutoSave(this.itemState, ADMINISTRATION_ITEMS_STATE); this.autoSaveService.withAutoSave(this.info, ADMINISTRATION_INFO); - this.permissionsResource.onDataUpdate.addHandler(() => { + this.permissionsResource.onDataUpdate.addPostHandler(() => { this.checkPermissions(this.screenService.routerService.state); }); } diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index d74b75845a..c76e3a614c 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -6,10 +6,11 @@ * you may not use this file except in compliance with the License. */ -import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-connections'; +import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; +import { isNavigatorViewSettingsEqual, CONNECTION_NAVIGATOR_VIEW_SETTINGS, NavigatorViewSettings } from '@cloudbeaver/core-root'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; diff --git a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts index 4fb1e76649..211eb0a9b8 100644 --- a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts +++ b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts @@ -315,6 +315,10 @@ export class NavigationTabsService { } private async restoreTab(tab: ITab, removedTabs: string[]): Promise { + if (tab.restored) { + return; + } + let restoreFail = false; try { const handler = this.handlers.get(tab.handlerId); diff --git a/webapp/packages/core-authentication/src/AuthInfoService.ts b/webapp/packages/core-authentication/src/AuthInfoService.ts index fbc3bd4d20..f8d0769674 100644 --- a/webapp/packages/core-authentication/src/AuthInfoService.ts +++ b/webapp/packages/core-authentication/src/AuthInfoService.ts @@ -21,8 +21,8 @@ export class AuthInfoService { return this.userInfoResource.data; } - async login(provider: string, credentials: Record): Promise { - return this.userInfoResource.login(provider, credentials); + async login(provider: string, credentials: Record, link?: boolean): Promise { + return this.userInfoResource.login(provider, credentials, link); } async logout(): Promise { diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 331a3fd846..34bba9c15b 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -23,9 +23,14 @@ export class UserInfoResource extends CachedDataResource super(null); this.sync(this.sessionResource); + this.sessionDataResource.beforeLoad.addHandler(() => this.load()); this.onDataOutdated.addHandler(() => this.sessionDataResource.markOutdated()); } + isLinked(provideId: string): boolean { + return this.data?.linkedAuthProviders.includes(provideId) || false; + } + getId(): string { return this.data?.userId || 'anonymous'; } @@ -40,7 +45,7 @@ export class UserInfoResource extends CachedDataResource || this.data.authTokens.some(token => token.origin.type === type && token.origin.subType === subType); } - async login(provider: string, credentials: Record): Promise { + async login(provider: string, credentials: Record, link?: boolean): Promise { await this.performUpdate(undefined, undefined, async () => { const processedCredentials = await this.authProviderService.processCredentials(provider, credentials); @@ -48,10 +53,11 @@ export class UserInfoResource extends CachedDataResource const { authToken } = await this.graphQLService.sdk.authLogin({ provider, credentials: processedCredentials, + linkUser: link, customIncludeOriginDetails: true, }); - if (this.data === null) { + if (this.data === null || link) { this.data = await this.loader(); } else { this.data.authTokens.push(authToken as UserAuthToken); @@ -67,9 +73,9 @@ export class UserInfoResource extends CachedDataResource if (this.data) { await this.graphQLService.sdk.authLogout(); this.data = null; - this.sessionDataResource.refresh(); } }); + await this.sessionDataResource.refresh(); } protected async loader(): Promise { diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 2084a4de93..f8f2b9b360 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -10,7 +10,7 @@ import { action, makeObservable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; -import { SessionDataResource } from '@cloudbeaver/core-root'; +import { NavigatorViewSettings, SessionDataResource } from '@cloudbeaver/core-root'; import { GraphQLService, CachedMapResource, @@ -26,7 +26,6 @@ import { } from '@cloudbeaver/core-sdk'; import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource'; -import type { NavigatorViewSettings } from './ConnectionNavigatorViewSettings'; export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] }; export type ConnectionInitConfig = Omit; diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 59b51d44b2..22a5209671 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -32,4 +32,3 @@ export * from './NetworkHandlerResource'; export * from './useConnectionInfo'; export * from './useDBDriver'; export * from './getUniqueConnectionName'; -export * from './ConnectionNavigatorViewSettings'; diff --git a/webapp/packages/core-di/src/App.ts b/webapp/packages/core-di/src/App.ts index 51bb6b627d..250ed1145e 100644 --- a/webapp/packages/core-di/src/App.ts +++ b/webapp/packages/core-di/src/App.ts @@ -7,6 +7,7 @@ */ import { Bootstrap } from './Bootstrap'; +import { Dependency } from './Dependency'; import type { DIContainer } from './DIContainer'; import { RootContainerService } from './entities/RootContainerService'; import type { IServiceCollection, IServiceInjector } from './IApp'; @@ -62,6 +63,8 @@ export class App { if ('register' in serviceInstance) { await serviceInstance.register(); } + } else if (service.prototype instanceof Dependency) { + this.diWrapper.injector.getServiceByClass(service); } } } diff --git a/webapp/packages/core-di/src/Dependency.ts b/webapp/packages/core-di/src/Dependency.ts new file mode 100644 index 0000000000..e95f7fc2ff --- /dev/null +++ b/webapp/packages/core-di/src/Dependency.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { injectable } from './injectable'; + +@injectable() +export abstract class Dependency { +} diff --git a/webapp/packages/core-di/src/index.ts b/webapp/packages/core-di/src/index.ts index af0b07fbd7..94c7ec9e62 100644 --- a/webapp/packages/core-di/src/index.ts +++ b/webapp/packages/core-di/src/index.ts @@ -2,6 +2,7 @@ export * from './IApp'; export * from './App'; export * from './AppContext'; export * from './Bootstrap'; +export * from './Dependency'; export * from './injectable'; export * from './PluginManifest'; export * from './useService'; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts index 22732aec9c..c9f8528602 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialogService.ts @@ -40,7 +40,7 @@ export interface DialogInternal { @injectable() export class CommonDialogService { - dialogs: DialogInternal[] = observable([]); + dialogs: DialogInternal[] = observable([], { deep: false }); // note that if dialog is closed by user it will be resolved with DialogueStateResult.Rejected async open( diff --git a/webapp/packages/core-executor/src/IExecutionContext.ts b/webapp/packages/core-executor/src/IExecutionContext.ts index 97963cdc0e..06e6dc06d9 100644 --- a/webapp/packages/core-executor/src/IExecutionContext.ts +++ b/webapp/packages/core-executor/src/IExecutionContext.ts @@ -12,7 +12,7 @@ export interface IContextGetter { } export interface IExecutionContextProvider { - hasContext: (token: IContextLoader) => boolean; + hasContext: (token: IContextLoader) => boolean; getContext: IContextGetter; } diff --git a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts index 4d5cfc465c..5db20bb715 100644 --- a/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts +++ b/webapp/packages/core-executor/src/TaskScheduler/TaskScheduler.ts @@ -17,6 +17,10 @@ export class TaskScheduler { return this.queue.map(task => task.id); } + get executing(): boolean { + return this.queue.length > 0; + } + private readonly queue: Array>; private readonly isBlocked: BlockedExecution | null; @@ -53,7 +57,6 @@ export class TaskScheduler { await error?.(exception); throw exception; } finally { - this.queue.splice(this.queue.indexOf(task), 1); await after?.(); } } @@ -72,18 +75,22 @@ export class TaskScheduler { id: TIdentifier, promise: () => Promise, ) { - if (!this.isBlocked) { - return promise(); + try { + if (!this.isBlocked) { + return await promise(); + } + + const queueList = this.queue.filter(active => this.isBlocked!(active.id, id)); + + for (const task of queueList) { + try { + await task.task; + } catch {} + } + + return await promise(); + } finally { + this.queue.splice(this.queue.findIndex(task => task.id === id), 1); } - - const queueList = this.queue.filter(active => this.isBlocked!(active.id, id)); - - for (const task of queueList) { - try { - await task.task; - } catch {} - } - - return promise(); } } diff --git a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts b/webapp/packages/core-root/src/ConnectionNavigatorViewSettings.ts similarity index 100% rename from webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts rename to webapp/packages/core-root/src/ConnectionNavigatorViewSettings.ts diff --git a/webapp/packages/core-root/src/ServerConfigResource.ts b/webapp/packages/core-root/src/ServerConfigResource.ts index 92fc289f26..ea133fb7b7 100644 --- a/webapp/packages/core-root/src/ServerConfigResource.ts +++ b/webapp/packages/core-root/src/ServerConfigResource.ts @@ -9,7 +9,9 @@ import { makeObservable, observable } from 'mobx'; import { injectable } from '@cloudbeaver/core-di'; -import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput } from '@cloudbeaver/core-sdk'; +import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput, NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; + +import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings'; @injectable() export class ServerConfigResource extends CachedDataResource { @@ -59,23 +61,73 @@ export class ServerConfigResource extends CachedDataResource { + async setDefaultNavigatorSettings(settings: NavigatorSettingsInput): Promise { + await this.performUpdate(undefined, undefined, async () => { + await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings }); + + if (this.data) { + this.data.defaultNavigatorSettings = { ...settings }; + } else { + this.data = await this.loader(); + } + }, () => !this.isNavigatorSettingsChanged(settings)); + } + + async save(onlyRestart = false): Promise { await this.performUpdate(undefined, undefined, async () => { await this.graphQLService.sdk.configureServer({ - configuration: this.update, + configuration: onlyRestart ? {} : this.update, }); this.data = await this.loader(); - }); + }, () => !this.isChanged() && !onlyRestart); } protected async loader(): Promise { const { serverConfig } = await this.graphQLService.sdk.serverConfig(); + this.syncUpdateData(serverConfig); + + return serverConfig as ServerConfig; + } + + private syncUpdateData(serverConfig: ServerConfig) { this.update.serverName = serverConfig.name; this.update.sessionExpireTime = serverConfig.sessionExpireTime; @@ -90,7 +142,5 @@ export class ServerConfigResource extends CachedDataResource { constructor( private graphQLService: GraphQLService, - private serverConfiguration: ServerConfigResource + serverConfiguration: ServerConfigResource ) { super(null); - this.sync(this.serverConfiguration); + this.sync(serverConfiguration); } protected async loader(): Promise { diff --git a/webapp/packages/core-root/src/index.ts b/webapp/packages/core-root/src/index.ts index 47de13727c..54c1c80c4f 100644 --- a/webapp/packages/core-root/src/index.ts +++ b/webapp/packages/core-root/src/index.ts @@ -1,3 +1,4 @@ +export * from './ConnectionNavigatorViewSettings'; export * from './PermissionsResource'; export * from './PermissionsService'; export * from './ServerConfigResource'; diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts index b0b2da2496..4b24063928 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts @@ -35,6 +35,7 @@ export abstract class CachedDataResource< markUpdated(param: TParam): void { const metadata = this.metadata.get(param as unknown as TKey); metadata.outdated = false; + metadata.exception = null; this.loaded = true; } diff --git a/webapp/packages/core-sdk/src/Resource/CachedResource.ts b/webapp/packages/core-sdk/src/Resource/CachedResource.ts index 0e0a976488..9c5ff0e9a8 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedResource.ts @@ -6,8 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable } from 'mobx'; +import { observable, makeObservable, action } from 'mobx'; +import { Dependency } from '@cloudbeaver/core-di'; import { Executor, ExecutorInterrupter, IExecutor, TaskScheduler } from '@cloudbeaver/core-executor'; import { MetadataMap } from '@cloudbeaver/core-utils'; @@ -44,7 +45,7 @@ export abstract class CachedResource< TParam, TKey = TParam, TContext = void -> { +> extends Dependency { data: TData; readonly onDataOutdated: IExecutor; @@ -54,15 +55,23 @@ export abstract class CachedResource< protected metadata: MetadataMap; - protected loading = false; + protected get loading(): boolean { + return this.scheduler.executing; + } protected scheduler: TaskScheduler; protected paramAliases: Array>; constructor(defaultValue: TData) { - makeObservable, 'loading'>(this, { + super(); + makeObservable, 'loader'>(this, { data: observable, - loading: observable, + loader: action, + markDataLoading: action, + markDataLoaded: action, + markDataError: action, + markOutdated: action, + markUpdated: action, }); this.includes = this.includes.bind(this); @@ -82,7 +91,7 @@ export abstract class CachedResource< resource: CachedResource, context: TContext ): void { - resource.onDataOutdated.addHandler(param => this.markOutdated(param)); + resource.onDataOutdated.addHandler(this.markOutdated.bind(this)); resource.onDataUpdate.addHandler(param => this.load(param, context)); this.beforeLoad.addHandler(param => resource.load(param)); } @@ -207,22 +216,24 @@ export abstract class CachedResource< } param = this.transformParam(param); + const contexts = await this.beforeLoad.execute(param); + + if (ExecutorInterrupter.isInterrupted(contexts)) { + return; + } + this.markDataLoading(param, context); - this.loading = true; return this.scheduler.schedule( param, async () => { - // repeated because previous task maybe has been load requested data + // repeated because previous task maybe has been load requested data if (exitCheck?.()) { return; } return await this.taskWrapper(param, context, update); }, - () => { - this.markDataLoaded(param, context); - this.loading = false; - }, + () => this.markDataLoaded(param, context), () => this.onDataUpdate.execute(param), exception => this.markDataError(exception, param, context)); } @@ -240,21 +251,17 @@ export abstract class CachedResource< } this.markDataLoading(param, context); - this.loading = true; await this.scheduler.schedule( param, async () => { - // repeated because previous task maybe has been load requested data + // repeated because previous task maybe has been load requested data if (this.isLoaded(param, context) && !this.isOutdated(param) && !refresh) { return; } await this.taskWrapper(param, context, this.loadingTask); }, - () => { - this.markDataLoaded(param, context); - this.loading = false; - }, + () => this.markDataLoaded(param, context), () => this.onDataUpdate.execute(param), exception => this.markDataError(exception, param, context)); } diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql index d4c30a474a..601c32a823 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogin.gql @@ -1,9 +1,10 @@ query authLogin( $provider: ID! $credentials: Object! + $linkUser: Boolean $customIncludeOriginDetails: Boolean! ) { - authToken: authLogin(provider: $provider, credentials: $credentials) { + authToken: authLogin(provider: $provider, credentials: $credentials, linkUser: $linkUser) { ...AuthToken } } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql b/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql index 9a7c1e3167..134e6717af 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/getActiveUser.gql @@ -2,6 +2,7 @@ query getActiveUser($customIncludeOriginDetails: Boolean!) { user: activeUser { userId displayName + linkedAuthProviders authTokens { ...AuthToken } diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 4cfa4ffe0d..1dde80cd51 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -77,6 +77,7 @@ export interface QueryAllConnectionsArgs { export interface QueryAuthLoginArgs { provider: Scalars['ID']; credentials: Scalars['Object']; + linkUser?: Maybe; } export interface QueryAuthLogoutArgs { @@ -935,6 +936,7 @@ export interface UserInfo { userId: Scalars['String']; displayName?: Maybe; authTokens: UserAuthToken[]; + linkedAuthProviders: Array; } export interface DataTransferProcessorInfo { @@ -967,6 +969,7 @@ export interface AsyncTaskCancelMutation { result: Mutation['asyncTaskCancel'] } export type AuthLoginQueryVariables = Exact<{ provider: Scalars['ID']; credentials: Scalars['Object']; + linkUser?: Maybe; customIncludeOriginDetails: Scalars['Boolean']; }>; @@ -982,7 +985,7 @@ export type GetActiveUserQueryVariables = Exact<{ export interface GetActiveUserQuery { user?: Maybe<( - Pick + Pick & { authTokens: AuthTokenFragment[] } )>; } @@ -1819,8 +1822,12 @@ export const AsyncTaskCancelDocument = ` } `; export const AuthLoginDocument = ` - query authLogin($provider: ID!, $credentials: Object!, $customIncludeOriginDetails: Boolean!) { - authToken: authLogin(provider: $provider, credentials: $credentials) { + query authLogin($provider: ID!, $credentials: Object!, $linkUser: Boolean, $customIncludeOriginDetails: Boolean!) { + authToken: authLogin( + provider: $provider + credentials: $credentials + linkUser: $linkUser + ) { ...AuthToken } } @@ -1835,6 +1842,7 @@ export const GetActiveUserDocument = ` user: activeUser { userId displayName + linkedAuthProviders authTokens { ...AuthToken } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx index 40d2177870..f24e95fca7 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx @@ -11,8 +11,8 @@ import React, { useCallback } from 'react'; import styled from 'reshadow'; import { BASE_CONTAINERS_STYLES, SwitchNew } from '@cloudbeaver/core-blocks'; -import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-connections'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-root'; import { useStyles } from '@cloudbeaver/core-theming'; import type { IServerConfigurationPageState } from '../IServerConfigurationPageState'; diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx index 37502ef053..9a966da4df 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationPage.tsx @@ -13,6 +13,7 @@ import { AdministrationItemContentComponent, AdministrationTools, ADMINISTRATION import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, GroupItem, GroupTitle, IconButton, Loader, Placeholder, SubmittingForm, useFocus, useFormValidator } from '@cloudbeaver/core-blocks'; import { useController, useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; import { useStyles } from '@cloudbeaver/core-theming'; import { ServerConfigurationConfigurationForm } from './Form/ServerConfigurationConfigurationForm'; @@ -42,16 +43,19 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser const translate = useTranslate(); const style = useStyles(styles, ADMINISTRATION_TOOLS_STYLES, BASE_CONTAINERS_STYLES); const [focusedRef] = useFocus({ focusFirstChild: true }); + const serverConfigResource = useService(ServerConfigResource); const service = useService(ServerConfigurationService); const controller = useController(ServerConfigurationPageController); + const changed = serverConfigResource.isChanged() + || serverConfigResource.isNavigatorSettingsChanged(service.state.navigatorConfig); useFormValidator(service.validationTask, focusedRef); return styled(style)( {controller.editing && ( - - + + )} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 403643e9b1..6de9745314 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -167,7 +167,7 @@ export class ServerConfigurationService { } try { - await this.graphQLService.sdk.setDefaultNavigatorSettings({ settings: data.state.navigatorConfig }); + await this.serverConfigResource.setDefaultNavigatorSettings(data.state.navigatorConfig); if (!data.configurationWizard || data.finish) { await this.serverConfigResource.save(); } diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index a43564f9c8..234609abcb 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -11,7 +11,7 @@ import { AppScreenService } from '@cloudbeaver/core-app'; import { AppAuthService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/core-executor'; +import type { IExecutorHandler } from '@cloudbeaver/core-executor'; import { SessionDataResource } from '@cloudbeaver/core-root'; import { ScreenService } from '@cloudbeaver/core-routing'; import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; @@ -37,8 +37,8 @@ export class AuthenticationService extends Bootstrap { this.authPromise = null; } - async authUser(provider: string | null = null): Promise { - await this.auth(false, provider); + async authUser(provider: string | null = null, link?: boolean): Promise { + await this.auth(false, provider, link); } async logout(): Promise { @@ -53,11 +53,11 @@ export class AuthenticationService extends Bootstrap { } } - private async auth(persistent: boolean, provider: string | null = null) { + private async auth(persistent: boolean, provider: string | null = null, link?: boolean) { if (this.authPromise) { return this.authPromise; } - this.authPromise = this.authDialogService.showLoginForm(persistent, provider); + this.authPromise = this.authDialogService.showLoginForm(persistent, provider, link); try { await this.authPromise; } finally { @@ -75,9 +75,6 @@ export class AuthenticationService extends Bootstrap { } register(): void { - this.sessionDataResource.beforeLoad.addHandler( - ExecutorInterrupter.interrupter(() => this.appAuthService.isAuthNeeded()) - ); this.sessionDataResource.beforeLoad.addPostHandler(() => { this.requireAuthentication(); }); this.appScreenService.activation.addHandler(() => this.requireAuthentication()); diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx index ae831e8b57..72f309eb4d 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.tsx @@ -85,20 +85,25 @@ const styles = composes( ` ); -export const AuthDialog: DialogComponent = observer(function AuthDialog({ - payload, +interface IAuthPayload { + provider: string | null; + link?: boolean; +} + +export const AuthDialog: DialogComponent = observer(function AuthDialog({ + payload: { provider, link }, options, rejectDialog, }) { const userInfo = useService(UserInfoResource); - const controller = useController(AuthDialogController, rejectDialog); + const controller = useController(AuthDialogController, link || false, rejectDialog); const translate = useTranslate(); - if (payload) { - controller.selectProvider(payload); + if (provider) { + controller.selectProvider(provider); } - const showTabs = !payload && controller.providers.length > 1; + const showTabs = !provider && controller.providers.length > 1; const additional = userInfo.data !== null && controller.provider?.id && !userInfo.hasToken(controller.provider?.id); diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts index 2eec317c51..eb38e982b1 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogController.ts @@ -33,6 +33,7 @@ export class AuthDialogController implements IInitializableController, IDestruct readonly error = new GQLErrorCatcher(); private isDistructed = false; + private link!: boolean; private close!: () => void; constructor( @@ -49,7 +50,8 @@ export class AuthDialogController implements IInitializableController, IDestruct }); } - init(onClose: () => void) { + init(link: boolean, onClose: () => void) { + this.link = link; this.close = onClose; this.loadProviders(); } @@ -65,7 +67,7 @@ export class AuthDialogController implements IInitializableController, IDestruct this.isAuthenticating = true; try { - await this.authInfoService.login(this.provider.id, this.credentials); + await this.authInfoService.login(this.provider.id, this.credentials, this.link); this.close(); } catch (exception) { if (!this.error.catch(exception) || this.isDistructed) { diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts index 018b1cfcff..d86acbe9c2 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialogService.ts @@ -17,7 +17,7 @@ export class AuthDialogService { private commonDialogService: CommonDialogService ) { } - async showLoginForm(persistent = false, provider: string | null = null): Promise { - await this.commonDialogService.open(AuthDialog, provider, { persistent }); + async showLoginForm(persistent = false, provider: string | null = null, link?: boolean): Promise { + await this.commonDialogService.open(AuthDialog, { provider, link }, { persistent }); } }