mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-24 23:05:29 +08:00
fix: CB-792 CB-730 CB-906 authentication & administration
This commit is contained in:
+1
-1
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -315,6 +315,10 @@ export class NavigationTabsService {
|
||||
}
|
||||
|
||||
private async restoreTab(tab: ITab, removedTabs: string[]): Promise<void> {
|
||||
if (tab.restored) {
|
||||
return;
|
||||
}
|
||||
|
||||
let restoreFail = false;
|
||||
try {
|
||||
const handler = this.handlers.get(tab.handlerId);
|
||||
|
||||
@@ -21,8 +21,8 @@ export class AuthInfoService {
|
||||
return this.userInfoResource.data;
|
||||
}
|
||||
|
||||
async login(provider: string, credentials: Record<string, string>): Promise<UserInfo | null> {
|
||||
return this.userInfoResource.login(provider, credentials);
|
||||
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
|
||||
return this.userInfoResource.login(provider, credentials, link);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
|
||||
@@ -23,9 +23,14 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
|
||||
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<UserInfo | null, void>
|
||||
|| this.data.authTokens.some(token => token.origin.type === type && token.origin.subType === subType);
|
||||
}
|
||||
|
||||
async login(provider: string, credentials: Record<string, string>): Promise<UserInfo | null> {
|
||||
async login(provider: string, credentials: Record<string, string>, link?: boolean): Promise<UserInfo | null> {
|
||||
await this.performUpdate(undefined, undefined, async () => {
|
||||
const processedCredentials = await this.authProviderService.processCredentials(provider, credentials);
|
||||
|
||||
@@ -48,10 +53,11 @@ export class UserInfoResource extends CachedDataResource<UserInfo | null, void>
|
||||
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<UserInfo | null, void>
|
||||
if (this.data) {
|
||||
await this.graphQLService.sdk.authLogout();
|
||||
this.data = null;
|
||||
this.sessionDataResource.refresh();
|
||||
}
|
||||
});
|
||||
await this.sessionDataResource.refresh();
|
||||
}
|
||||
|
||||
protected async loader(): Promise<UserInfo | null> {
|
||||
|
||||
@@ -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<InitConnectionMutationVariables, 'includeOrigin' | 'customIncludeOriginDetails' | 'includeAuthProperties' | 'customIncludeNetworkHandlerCredentials'>;
|
||||
|
||||
@@ -32,4 +32,3 @@ export * from './NetworkHandlerResource';
|
||||
export * from './useConnectionInfo';
|
||||
export * from './useDBDriver';
|
||||
export * from './getUniqueConnectionName';
|
||||
export * from './ConnectionNavigatorViewSettings';
|
||||
|
||||
@@ -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<Bootstrap>(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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<TPayload, TResult>(
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface IContextGetter<TData> {
|
||||
}
|
||||
|
||||
export interface IExecutionContextProvider<TData> {
|
||||
hasContext: (token: IContextLoader<TData>) => boolean;
|
||||
hasContext: (token: IContextLoader<any, TData>) => boolean;
|
||||
getContext: IContextGetter<TData>;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ export class TaskScheduler<TIdentifier> {
|
||||
return this.queue.map(task => task.id);
|
||||
}
|
||||
|
||||
get executing(): boolean {
|
||||
return this.queue.length > 0;
|
||||
}
|
||||
|
||||
private readonly queue: Array<ITask<TIdentifier>>;
|
||||
|
||||
private readonly isBlocked: BlockedExecution<TIdentifier> | null;
|
||||
@@ -53,7 +57,6 @@ export class TaskScheduler<TIdentifier> {
|
||||
await error?.(exception);
|
||||
throw exception;
|
||||
} finally {
|
||||
this.queue.splice(this.queue.indexOf(task), 1);
|
||||
await after?.();
|
||||
}
|
||||
}
|
||||
@@ -72,18 +75,22 @@ export class TaskScheduler<TIdentifier> {
|
||||
id: TIdentifier,
|
||||
promise: () => Promise<T>,
|
||||
) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ServerConfig | null, void> {
|
||||
@@ -59,23 +61,73 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|
||||
return this.update.publicCredentialsSaveEnabled ?? this.data?.publicCredentialsSaveEnabled ?? false;
|
||||
}
|
||||
|
||||
isChanged(): boolean {
|
||||
if (!this.data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.update.adminName || this.update.adminPassword) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
this.update.serverName !== this.data.name
|
||||
|| this.update.sessionExpireTime !== this.data.sessionExpireTime
|
||||
|
||||
|| this.update.anonymousAccessEnabled !== this.data.anonymousAccessEnabled
|
||||
|| this.update.authenticationEnabled !== this.data.authenticationEnabled
|
||||
|
||||
|| this.update.adminCredentialsSaveEnabled !== this.data.adminCredentialsSaveEnabled
|
||||
|| this.update.publicCredentialsSaveEnabled !== this.data.publicCredentialsSaveEnabled
|
||||
|
||||
|| this.update.customConnectionsEnabled !== this.data.supportsCustomConnections
|
||||
|| this.update.enabledAuthProviders !== this.data.enabledAuthProviders
|
||||
);
|
||||
}
|
||||
|
||||
isNavigatorSettingsChanged(settings: NavigatorSettingsInput): boolean {
|
||||
if (!this.data?.defaultNavigatorSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isNavigatorViewSettingsEqual(this.data.defaultNavigatorSettings, settings);
|
||||
}
|
||||
|
||||
setDataUpdate(update: ServerConfigInput): void {
|
||||
this.update = update;
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
async setDefaultNavigatorSettings(settings: NavigatorSettingsInput): Promise<void> {
|
||||
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<void> {
|
||||
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<ServerConfig> {
|
||||
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<ServerConfig | null
|
||||
|
||||
this.update.customConnectionsEnabled = serverConfig.supportsCustomConnections;
|
||||
this.update.enabledAuthProviders = serverConfig.enabledAuthProviders;
|
||||
|
||||
return serverConfig as ServerConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ export type SessionState = SessionStateFragment;
|
||||
export class SessionResource extends CachedDataResource<SessionState | null, void> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private serverConfiguration: ServerConfigResource
|
||||
serverConfiguration: ServerConfigResource
|
||||
) {
|
||||
super(null);
|
||||
|
||||
this.sync(this.serverConfiguration);
|
||||
this.sync(serverConfiguration);
|
||||
}
|
||||
|
||||
protected async loader(): Promise<SessionState> {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './ConnectionNavigatorViewSettings';
|
||||
export * from './PermissionsResource';
|
||||
export * from './PermissionsService';
|
||||
export * from './ServerConfigResource';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TParam>;
|
||||
@@ -54,15 +55,23 @@ export abstract class CachedResource<
|
||||
|
||||
protected metadata: MetadataMap<TKey, ICachedResourceMetadata>;
|
||||
|
||||
protected loading = false;
|
||||
protected get loading(): boolean {
|
||||
return this.scheduler.executing;
|
||||
}
|
||||
|
||||
protected scheduler: TaskScheduler<TParam>;
|
||||
protected paramAliases: Array<IParamAlias<TParam>>;
|
||||
|
||||
constructor(defaultValue: TData) {
|
||||
makeObservable<CachedResource<TData, TParam, TKey, TContext>, 'loading'>(this, {
|
||||
super();
|
||||
makeObservable<CachedResource<TData, TParam, TKey, TContext>, '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<any, TParam, TParam, void>,
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ query getActiveUser($customIncludeOriginDetails: Boolean!) {
|
||||
user: activeUser {
|
||||
userId
|
||||
displayName
|
||||
linkedAuthProviders
|
||||
authTokens {
|
||||
...AuthToken
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ export interface QueryAllConnectionsArgs {
|
||||
export interface QueryAuthLoginArgs {
|
||||
provider: Scalars['ID'];
|
||||
credentials: Scalars['Object'];
|
||||
linkUser?: Maybe<Scalars['Boolean']>;
|
||||
}
|
||||
|
||||
export interface QueryAuthLogoutArgs {
|
||||
@@ -935,6 +936,7 @@ export interface UserInfo {
|
||||
userId: Scalars['String'];
|
||||
displayName?: Maybe<Scalars['String']>;
|
||||
authTokens: UserAuthToken[];
|
||||
linkedAuthProviders: Array<Scalars['String']>;
|
||||
}
|
||||
|
||||
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<Scalars['Boolean']>;
|
||||
customIncludeOriginDetails: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
@@ -982,7 +985,7 @@ export type GetActiveUserQueryVariables = Exact<{
|
||||
|
||||
export interface GetActiveUserQuery {
|
||||
user?: Maybe<(
|
||||
Pick<UserInfo, 'userId' | 'displayName'>
|
||||
Pick<UserInfo, 'userId' | 'displayName' | 'linkedAuthProviders'>
|
||||
& { 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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+6
-2
@@ -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<HTMLFormElement>({ 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)(
|
||||
<SubmittingForm ref={focusedRef} name='server_config' onSubmit={controller.save} onChange={controller.change}>
|
||||
{controller.editing && (
|
||||
<AdministrationTools>
|
||||
<IconButton name="admin-save" viewBox="0 0 28 28" onClick={controller.save} />
|
||||
<IconButton name="admin-cancel" viewBox="0 0 28 28" onClick={controller.reset} />
|
||||
<IconButton name="admin-save" viewBox="0 0 28 28" disabled={!changed} onClick={controller.save} />
|
||||
<IconButton name="admin-cancel" viewBox="0 0 28 28" disabled={!changed} onClick={controller.reset} />
|
||||
</AdministrationTools>
|
||||
)}
|
||||
<ColoredContainer wrap gap overflow parent>
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<void> {
|
||||
await this.auth(false, provider);
|
||||
async authUser(provider: string | null = null, link?: boolean): Promise<void> {
|
||||
await this.auth(false, provider, link);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
@@ -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());
|
||||
|
||||
@@ -85,20 +85,25 @@ const styles = composes(
|
||||
`
|
||||
);
|
||||
|
||||
export const AuthDialog: DialogComponent<string | null, null> = observer(function AuthDialog({
|
||||
payload,
|
||||
interface IAuthPayload {
|
||||
provider: string | null;
|
||||
link?: boolean;
|
||||
}
|
||||
|
||||
export const AuthDialog: DialogComponent<IAuthPayload, null> = 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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class AuthDialogService {
|
||||
private commonDialogService: CommonDialogService
|
||||
) { }
|
||||
|
||||
async showLoginForm(persistent = false, provider: string | null = null): Promise<void> {
|
||||
await this.commonDialogService.open(AuthDialog, provider, { persistent });
|
||||
async showLoginForm(persistent = false, provider: string | null = null, link?: boolean): Promise<void> {
|
||||
await this.commonDialogService.open(AuthDialog, { provider, link }, { persistent });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user