mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
@@ -11,7 +11,7 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { usePermission, EPermission } from '@cloudbeaver/core-root';
|
||||
|
||||
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
|
||||
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
|
||||
import { useChildren } from '../shared/useChildren';
|
||||
import { NavigationTreeNode } from './NavigationTreeNode/NavigationTreeNode';
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export function useNavigationTree(nodeId: string, parentId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const [isExpanded, switchExpand] = useState(false);
|
||||
const [isSelected, switchSelect] = useState(false);
|
||||
const { node, isLoaded: nodeLoaded } = useNode(nodeId);
|
||||
const { node, isOutdated } = useNode(nodeId);
|
||||
const children = useChildren(nodeId);
|
||||
|
||||
if (!node) {
|
||||
@@ -81,12 +81,12 @@ export function useNavigationTree(nodeId: string, parentId: string) {
|
||||
}, [isExpandable && hasChildren]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpandedFiltered && !children.isLoaded && !children.isLoading && !!children.children && nodeLoaded) {
|
||||
if (isExpandedFiltered && children.isOutdated && !children.isLoading && children.isLoaded && !isOutdated) {
|
||||
navigationTreeService
|
||||
.loadNestedNodes(nodeId)
|
||||
.then(state => !state && switchExpand(false));
|
||||
}
|
||||
}, [isExpandedFiltered, children.isLoaded, children.isLoading, children.children, nodeLoaded, nodeId]);
|
||||
}, [isExpandedFiltered, children.isOutdated, children.isLoading, children.children, isOutdated, nodeId]);
|
||||
|
||||
// Here we subscribe to selected nodes if current node selected (mobx)
|
||||
if (isSelected && !navigationTreeService.isNodeSelected(nodeId)) {
|
||||
|
||||
@@ -14,7 +14,8 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
|
||||
import { ConnectionsManagerService } from '../shared/ConnectionsManager/ConnectionsManagerService';
|
||||
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
|
||||
import { NavNodeManagerService, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeManagerService';
|
||||
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
|
||||
import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService';
|
||||
import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils';
|
||||
|
||||
@injectable()
|
||||
|
||||
+3
-2
@@ -13,7 +13,8 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { IExtension } from '@cloudbeaver/core-extensions';
|
||||
|
||||
import { NavigationTreeService } from '../../NavigationTree/NavigationTreeService';
|
||||
import { ConnectionsManagerService, ObjectContainer } from '../../shared/ConnectionsManager/ConnectionsManagerService';
|
||||
import { ConnectionsManagerService } from '../../shared/ConnectionsManager/ConnectionsManagerService';
|
||||
import { ObjectContainer } from '../../shared/ConnectionsManager/ContainerResource';
|
||||
import { isConnectionProvider, IConnectionProvider } from '../../shared/ConnectionsManager/extensions/IConnectionProvider';
|
||||
import { isConnectionSetter, IConnectionSetter } from '../../shared/ConnectionsManager/extensions/IConnectionSetter';
|
||||
import { ITab } from '../../shared/NavigationTabs/ITab';
|
||||
@@ -184,7 +185,7 @@ export class ConnectionSchemaManagerService {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.connectionsManagerService.dbDrivers.load();
|
||||
await this.connectionsManagerService.dbDrivers.load('');
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load database drivers', true);
|
||||
}
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ import { computed } from 'mobx';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ComputedMenuItemModel, ComputedMenuPanelModel, IMenuItem } from '@cloudbeaver/core-dialogs';
|
||||
|
||||
import { ConnectionsManagerService, Connection } from '../../../shared/ConnectionsManager/ConnectionsManagerService';
|
||||
import { Connection } from '../../../shared/ConnectionsManager/ConnectionInfoResource';
|
||||
import { ConnectionsManagerService } from '../../../shared/ConnectionsManager/ConnectionsManagerService';
|
||||
import { EObjectFeature } from '../../../shared/NodesManager/EObjectFeature';
|
||||
import { NodeManagerUtils } from '../../../shared/NodesManager/NodeManagerUtils';
|
||||
import { ConnectionSchemaManagerService } from '../ConnectionSchemaManagerService';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { IComputedMenuItemOptions, StaticMenu } from '@cloudbeaver/core-dialogs';
|
||||
import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { ServerService } from '@cloudbeaver/core-root';
|
||||
import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { ThemeService } from '@cloudbeaver/core-theming';
|
||||
|
||||
@injectable()
|
||||
@@ -23,7 +23,7 @@ export class SettingsMenuService extends Bootstrap {
|
||||
constructor(
|
||||
private localizationService: LocalizationService,
|
||||
private themeService: ThemeService,
|
||||
private serverService: ServerService
|
||||
private serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -70,7 +70,7 @@ export class SettingsMenuService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async addLocales() {
|
||||
const config = await this.serverService.config.load();
|
||||
const config = await this.serverConfigResource.load(null);
|
||||
|
||||
if (!config) {
|
||||
return;
|
||||
|
||||
@@ -8,7 +8,10 @@ export * from './TopNavBar/MainMenu/MainMenuService';
|
||||
export * from './TopNavBar/SettingsMenu/SettingsMenuService';
|
||||
export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService';
|
||||
export * from './TopNavBar/TopNavBarService';
|
||||
export * from './shared/ConnectionsManager/ConnectionInfoResource';
|
||||
export * from './shared/ConnectionsManager/ConnectionsManagerService';
|
||||
export * from './shared/ConnectionsManager/ContainerResource';
|
||||
export * from './shared/ConnectionsManager/DBDriverResource';
|
||||
export * from './shared/ConnectionsManager/ConnectionDialogsService';
|
||||
export * from './shared/ConnectionsManager/extensions/IConnectionProvider';
|
||||
export * from './shared/ConnectionsManager/extensions/IConnectionSetter';
|
||||
@@ -17,7 +20,9 @@ export * from './shared/NodesManager/extensions/IObjectCatalogSetter';
|
||||
export * from './shared/NodesManager/extensions/IObjectSchemaProvider';
|
||||
export * from './shared/NodesManager/extensions/IObjectSchemaSetter';
|
||||
export * from './shared/NodesManager/DBObjectService';
|
||||
export * from './shared/NodesManager/NavNodeInfoResource';
|
||||
export * from './shared/NodesManager/NavNodeManagerService';
|
||||
export * from './shared/NodesManager/NavTreeResource';
|
||||
export * from './shared/NodesManager/NavNodeExtensionsService';
|
||||
export * from './shared/NodesManager/NodeManagerUtils';
|
||||
export * from './shared/Navigation/NavigationService';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
ConnectionInfo,
|
||||
GraphQLService,
|
||||
CachedMapResource,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type Connection = Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>
|
||||
|
||||
@injectable()
|
||||
export class ConnectionInfoResource extends CachedMapResource<string, Connection> {
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
super(new Map());
|
||||
}
|
||||
|
||||
protected async loader(connectionId: string): Promise<Map<string, Connection>> {
|
||||
const { connection } = await this.graphQLService.gql.connectionState({ id: connectionId });
|
||||
this.set(connectionId, connection);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
+25
-181
@@ -11,81 +11,20 @@ import { Subject } from 'rxjs';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { SessionService } from '@cloudbeaver/core-root';
|
||||
import {
|
||||
ConnectionInfo,
|
||||
DataSourceInfo,
|
||||
DriverInfo,
|
||||
GraphQLService,
|
||||
CachedResource,
|
||||
DatabaseObjectInfo,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
import { SessionResource } from '@cloudbeaver/core-root';
|
||||
import { DataSourceInfo, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { NavNodeManagerService } from '../NodesManager/NavNodeManagerService';
|
||||
import { NodeManagerUtils } from '../NodesManager/NodeManagerUtils';
|
||||
import { ConnectionInfoResource, Connection } from './ConnectionInfoResource';
|
||||
import { ContainerResource, ObjectContainer } from './ContainerResource';
|
||||
import { DBDriverResource, DBDriver } from './DBDriverResource';
|
||||
import { EConnectionFeature } from './EConnectionFeature';
|
||||
|
||||
export type DBDriver = Pick<
|
||||
DriverInfo,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'icon'
|
||||
| 'description'
|
||||
| 'defaultPort'
|
||||
| 'sampleURL'
|
||||
| 'embedded'
|
||||
| 'anonymousAccess'
|
||||
| 'promotedScore'
|
||||
>
|
||||
export type DBSource = Pick<DataSourceInfo, 'id' | 'name' | 'driverId' | 'description'>
|
||||
export type Connection = Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>
|
||||
export type ObjectContainer = Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>
|
||||
|
||||
type ConnectionLoadArgs = {
|
||||
connectionId: string;
|
||||
close?: boolean;
|
||||
remove?: boolean;
|
||||
connection?: never;
|
||||
}
|
||||
|
||||
type ConnectionSetArgs = {
|
||||
connection: Connection;
|
||||
connectionId?: never;
|
||||
remove?: never;
|
||||
close?: never;
|
||||
}
|
||||
|
||||
type DBDriversMetadata = {
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
type ConnectionInfoMetadata = {
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ConnectionsManagerService {
|
||||
readonly dbDrivers = new CachedResource(
|
||||
new Map(),
|
||||
this.refreshDriversAsync.bind(this),
|
||||
(_, { loaded }) => loaded,
|
||||
{ loaded: false }
|
||||
);
|
||||
readonly connectionInfo = new CachedResource(
|
||||
new Map(),
|
||||
this.loadConnectionInfo.bind(this),
|
||||
(_, metadata, args) => metadata.get(args.connectionId || args.connection!.id).loaded,
|
||||
new MetadataMap<string, ConnectionInfoMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, args) => metadata.get(args.connectionId || args.connection!.id).loading
|
||||
);
|
||||
readonly connectionObjectContainers = new CachedResource(
|
||||
new Map(),
|
||||
this.refreshObjectContainersAsync.bind(this),
|
||||
this.isObjectContainersLoaded.bind(this)
|
||||
);
|
||||
|
||||
@computed get connections(): Connection[] {
|
||||
return Array.from(this.connectionInfo.data.values());
|
||||
}
|
||||
@@ -95,11 +34,14 @@ export class ConnectionsManagerService {
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
readonly connectionInfo: ConnectionInfoResource,
|
||||
readonly connectionObjectContainers: ContainerResource,
|
||||
readonly dbDrivers: DBDriverResource,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private sessionService: SessionService,
|
||||
private sessionResource: SessionResource,
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
this.sessionService.onUpdate.subscribe(this.restoreConnections.bind(this));
|
||||
this.sessionResource.onDataUpdate.subscribe(this.restoreConnections.bind(this));
|
||||
}
|
||||
|
||||
getDBDrivers(): Map<string, DBDriver> {
|
||||
@@ -107,28 +49,20 @@ export class ConnectionsManagerService {
|
||||
}
|
||||
|
||||
async loadConnectionInfoAsync(connectionId: string): Promise<Connection> {
|
||||
const connections = await this.connectionInfo.load({ connectionId });
|
||||
|
||||
return connections.get(connectionId)!;
|
||||
return this.connectionInfo.load(connectionId);
|
||||
}
|
||||
|
||||
async refreshConnectionInfoAsync(connectionId: string): Promise<Connection> {
|
||||
const connections = await this.connectionInfo.refresh(true, { connectionId });
|
||||
|
||||
return connections.get(connectionId)!;
|
||||
return this.connectionInfo.refresh(connectionId);
|
||||
}
|
||||
|
||||
async loadDriversAsync(): Promise<Map<string, DBDriver>> {
|
||||
return this.dbDrivers.load();
|
||||
await this.dbDrivers.load('');
|
||||
return this.dbDrivers.data;
|
||||
}
|
||||
|
||||
async addOpenedConnection(connection: Connection) {
|
||||
await this.connectionInfo.refresh(
|
||||
true,
|
||||
{
|
||||
connection,
|
||||
}
|
||||
);
|
||||
this.connectionInfo.set(connection.id, connection);
|
||||
this.onOpenConnection.next(connection);
|
||||
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must also request node info and add it to nodes manager
|
||||
}
|
||||
@@ -165,7 +99,7 @@ export class ConnectionsManagerService {
|
||||
async closeConnectionAsync(id: string, skipNodesRefresh?: boolean): Promise<void> {
|
||||
await this.graphQLService.gql.closeConnection({ id });
|
||||
await this.afterConnectionClose(id);
|
||||
await this.connectionInfo.refresh(true, { connectionId: id, close: true });
|
||||
this.connectionInfo.delete(id);
|
||||
|
||||
if (!skipNodesRefresh) {
|
||||
await this.navNodeManagerService.updateRootChildren(); // Update connections list, probably here we must just remove nodes from nodes manager
|
||||
@@ -182,7 +116,9 @@ export class ConnectionsManagerService {
|
||||
try {
|
||||
await this.graphQLService.gql.closeConnection({ id: connectionId });
|
||||
await this.afterConnectionClose(connectionId);
|
||||
await this.connectionInfo.refresh(true, { connectionId, close: true });
|
||||
if (connection?.features.includes(EConnectionFeature.temporary)) {
|
||||
this.connectionInfo.delete(connectionId);
|
||||
}
|
||||
|
||||
if (connection.features.includes(EConnectionFeature.temporary)) {
|
||||
const node = this.navNodeManagerService.getNode(navNodeId);
|
||||
@@ -200,17 +136,17 @@ export class ConnectionsManagerService {
|
||||
}
|
||||
|
||||
async loadObjectContainer(connectionId: string, catalogId?: string): Promise<ObjectContainer[]> {
|
||||
const data = await this.connectionObjectContainers.load(connectionId, catalogId);
|
||||
return data.get(connectionId)!;
|
||||
await this.connectionObjectContainers.load({ connectionId, catalogId });
|
||||
return this.connectionObjectContainers.data.get(connectionId)!;
|
||||
}
|
||||
|
||||
private async afterConnectionClose(id: string) {
|
||||
await this.navNodeManagerService.removeTree(id);
|
||||
this.navNodeManagerService.removeTree(id);
|
||||
this.onCloseConnection.next(id);
|
||||
}
|
||||
|
||||
private async restoreConnections() {
|
||||
const config = await this.sessionService.session.load();
|
||||
const config = await this.sessionResource.load(null);
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
@@ -224,106 +160,14 @@ export class ConnectionsManagerService {
|
||||
|
||||
for (const connection of connectionsToRemove) {
|
||||
await this.afterConnectionClose(connection.id);
|
||||
await this.connectionInfo.refresh(true, {
|
||||
connectionId: connection.id,
|
||||
remove: true,
|
||||
});
|
||||
this.connectionInfo.delete(connection.id);
|
||||
}
|
||||
|
||||
await this.navNodeManagerService.updateRootChildren();
|
||||
}
|
||||
|
||||
private isObjectContainersLoaded(
|
||||
data: Map<string, ObjectContainer[]>,
|
||||
metadata: {},
|
||||
connectionId: string,
|
||||
catalogId?: string,
|
||||
) {
|
||||
return data.has(connectionId);
|
||||
}
|
||||
|
||||
private async refreshObjectContainersAsync(
|
||||
data: Map<string, ObjectContainer[]>,
|
||||
metadata: {},
|
||||
update: boolean,
|
||||
connectionId: string,
|
||||
catalogId?: string,
|
||||
): Promise<Map<string, ObjectContainer[]>> {
|
||||
const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({
|
||||
connectionId,
|
||||
catalogId,
|
||||
});
|
||||
data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async refreshDriversAsync(
|
||||
data: Map<string, DBDriver>,
|
||||
metadata: DBDriversMetadata,
|
||||
update: boolean
|
||||
): Promise<Map<string, DBDriver>> {
|
||||
const { driverList } = await this.graphQLService.gql.driverList();
|
||||
|
||||
data.clear();
|
||||
|
||||
for (const driver of driverList) {
|
||||
data.set(driver.id, driver);
|
||||
}
|
||||
metadata.loaded = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
private async loadConnectionInfo(
|
||||
data: Map<string, Connection>,
|
||||
metadata: MetadataMap<string, ConnectionInfoMetadata>,
|
||||
load: boolean,
|
||||
args: ConnectionLoadArgs | ConnectionSetArgs,
|
||||
): Promise<Map<string, Connection>> {
|
||||
let connectionId: string;
|
||||
if (args.connection) {
|
||||
connectionId = args.connection.id;
|
||||
} else {
|
||||
connectionId = args.connectionId;
|
||||
}
|
||||
const connectionInfo = data.get(connectionId);
|
||||
|
||||
const itemMetadata = metadata.get(connectionId);
|
||||
|
||||
if (args.connection) {
|
||||
data.set(connectionId, args.connection);
|
||||
itemMetadata.loaded = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
if (args.remove || (args.close && connectionInfo?.features.includes(EConnectionFeature.temporary))) {
|
||||
data.delete(connectionId);
|
||||
metadata.delete(connectionId);
|
||||
return data;
|
||||
}
|
||||
|
||||
if (load) {
|
||||
try {
|
||||
itemMetadata.loading = true;
|
||||
const { connection } = await this.graphQLService.gql.connectionState({ id: connectionId });
|
||||
|
||||
data.set(connectionId, connection);
|
||||
itemMetadata.loaded = true;
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private async restoreConnection(connection: Connection) {
|
||||
await this.connectionInfo.refresh(
|
||||
true,
|
||||
{
|
||||
connection,
|
||||
}
|
||||
);
|
||||
this.connectionInfo.set(connection.id, connection);
|
||||
this.onOpenConnection.next(connection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
DatabaseObjectInfo,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type ObjectContainer = Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>
|
||||
|
||||
type ObjectContainerParams = {
|
||||
connectionId: string;
|
||||
catalogId?: string;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class ContainerResource extends CachedDataResource<Map<string, ObjectContainer[]>, ObjectContainerParams> {
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
super(new Map());
|
||||
}
|
||||
|
||||
isLoaded({ connectionId }: ObjectContainerParams) {
|
||||
return this.data.has(connectionId);
|
||||
}
|
||||
|
||||
protected async loader({ connectionId, catalogId }: ObjectContainerParams): Promise<Map<string, ObjectContainer[]>> {
|
||||
const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({
|
||||
connectionId,
|
||||
catalogId,
|
||||
});
|
||||
this.data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedMapResource,
|
||||
DriverInfo,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type DBDriver = Pick<
|
||||
DriverInfo,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'icon'
|
||||
| 'description'
|
||||
| 'defaultPort'
|
||||
| 'sampleURL'
|
||||
| 'embedded'
|
||||
| 'anonymousAccess'
|
||||
| 'promotedScore'
|
||||
>
|
||||
|
||||
@injectable()
|
||||
export class DBDriverResource extends CachedMapResource<string, DBDriver> {
|
||||
@observable private loaded = false;
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
super(new Map());
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
protected async loader(param: string): Promise<Map<string, DBDriver>> {
|
||||
const { driverList } = await this.graphQLService.gql.driverList();
|
||||
|
||||
this.data.clear();
|
||||
|
||||
for (const driver of driverList) {
|
||||
this.data.set(driver.id, driver);
|
||||
}
|
||||
this.loaded = true;
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ export function useConnectionInfo(connectionId: string) {
|
||||
const connectionInfo = service.getConnectionById(connectionId);
|
||||
const load = useCallback(() => service.loadConnectionInfoAsync(connectionId), [service, connectionId]);
|
||||
const refresh = useCallback(() => service.refreshConnectionInfoAsync(connectionId), [service, connectionId]);
|
||||
const isLoading = useCallback(() => service.connectionInfo.isDataLoading({ connectionId }), [service, connectionId]);
|
||||
const isLoaded = useCallback(() => service.connectionInfo.isLoaded({ connectionId }), [service, connectionId]);
|
||||
const isLoading = useCallback(() => service.connectionInfo.isDataLoading(connectionId), [service, connectionId]);
|
||||
const isLoaded = useCallback(() => service.connectionInfo.isLoaded(connectionId), [service, connectionId]);
|
||||
|
||||
return {
|
||||
connectionInfo,
|
||||
|
||||
@@ -178,7 +178,7 @@ export class NavigationTabsService {
|
||||
// must be executed with low priority, because this call runs many requests to backend and blocks others
|
||||
async restoreTabs() {
|
||||
const removedTabs: string[] = [];
|
||||
const session = await this.sessionService.session.load();
|
||||
const session = await this.sessionService.session.load(null);
|
||||
|
||||
for (const tabId of this.state.tabs) {
|
||||
if (session?.cacheExpired) {
|
||||
|
||||
@@ -7,128 +7,70 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
import {
|
||||
GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DBObject } from './EntityTypes';
|
||||
|
||||
export interface IDBObjectParams {
|
||||
navNodeId: string[];
|
||||
remove?: boolean;
|
||||
parentId?: never;
|
||||
}
|
||||
|
||||
export interface IDBObjectValueParams {
|
||||
parentId: string;
|
||||
navNodeId: string[];
|
||||
remove?: never;
|
||||
}
|
||||
|
||||
interface IDBObjectMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
import { NavNodeInfoResource } from './NavNodeInfoResource';
|
||||
|
||||
@injectable()
|
||||
export class DBObjectService {
|
||||
readonly dbObject = new CachedResource(
|
||||
new Map(),
|
||||
this.loadDBObject.bind(this),
|
||||
(_, metadata, { navNodeId }) => navNodeId.every(navNodeId => metadata.get(navNodeId).loaded),
|
||||
new MetadataMap<string, IDBObjectMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, { navNodeId }) => navNodeId.some(navNodeId => metadata.get(navNodeId).loading)
|
||||
)
|
||||
|
||||
constructor(private graphQLService: GraphQLService) { }
|
||||
|
||||
getDBObject(navNodeId: string) {
|
||||
return this.dbObject.data.get(navNodeId);
|
||||
export class DBObjectService extends CachedMapResource<string, DBObject> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private navNodeInfoResource: NavNodeInfoResource,
|
||||
) {
|
||||
super(new Map());
|
||||
this.navNodeInfoResource.onDataOutdated.subscribe(key => this.markOutdated(key));
|
||||
this.navNodeInfoResource.onItemDelete.subscribe(key => this.delete(key));
|
||||
}
|
||||
|
||||
async load(navNodeId: string): Promise< DBObject>
|
||||
async load(navNodeId: string[], parentId?: string): Promise<DBObject[]>
|
||||
async load(navNodeId: string | string[], parentId?: string): Promise<DBObject | DBObject[]> {
|
||||
const dbObject = await this.dbObject.load({
|
||||
navNodeId: Array.isArray(navNodeId) ? navNodeId : [navNodeId],
|
||||
parentId,
|
||||
async loadChildren(parentId: string, key: ResourceKey<string>) {
|
||||
if (this.isLoaded(key) && !this.isOutdated(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.waitActive();
|
||||
await this.setActivePromise(key, this.loadFromChildren(parentId));
|
||||
this.dataSubject.next(this.data);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private async loadFromChildren(parentId: string) {
|
||||
const { dbObjects } = await this.graphQLService.gql.getChildrenDBObjectInfo({
|
||||
navNodeId: parentId,
|
||||
});
|
||||
|
||||
if (!Array.isArray(navNodeId)) {
|
||||
return dbObject.get(navNodeId)!;
|
||||
for (const dbObject of dbObjects) {
|
||||
this.set(
|
||||
dbObject.id,
|
||||
{
|
||||
navNodeId: dbObject.id,
|
||||
...dbObject.object,
|
||||
}
|
||||
);
|
||||
}
|
||||
return navNodeId.map(navNodeId => dbObject.get(navNodeId)!);
|
||||
}
|
||||
|
||||
async remove(navNodeId: string[]) {
|
||||
await this.dbObject.refresh(true, { navNodeId, remove: true });
|
||||
protected async loader(key: ResourceKey<string>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
const values: DBObject[] = [];
|
||||
for (const navNodeId of key.list) {
|
||||
values.push(await this.loadDBObjectInfo(navNodeId));
|
||||
}
|
||||
this.set(key, values);
|
||||
} else {
|
||||
this.set(key, await this.loadDBObjectInfo(key));
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private async loadDBObject(
|
||||
dbObject: Map<string, DBObject>,
|
||||
metadata: MetadataMap<string, IDBObjectMetadata>,
|
||||
load: boolean,
|
||||
data: IDBObjectParams | IDBObjectValueParams
|
||||
) {
|
||||
private async loadDBObjectInfo(navNodeId: string): Promise<DBObject> {
|
||||
const { objectInfo: { object } } = await this.graphQLService.gql.getDBObjectInfo({
|
||||
navNodeId,
|
||||
});
|
||||
|
||||
if (data.parentId) {
|
||||
for (const navNodeId of data.navNodeId) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (load) {
|
||||
try {
|
||||
const { dbObjects } = await this.graphQLService.gql.getChildrenDBObjectInfo({
|
||||
navNodeId: data.parentId,
|
||||
});
|
||||
|
||||
for (const navNodeId of data.navNodeId) {
|
||||
const data = dbObjects.find(dbObject => dbObject.id === navNodeId);
|
||||
|
||||
if (data) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
dbObject.set(navNodeId, { navNodeId, ...data.object } as DBObject);
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
for (const navNodeId of data.navNodeId) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dbObject;
|
||||
}
|
||||
|
||||
for (const navNodeId of data.navNodeId) {
|
||||
if (data.remove) {
|
||||
dbObject.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
} else {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
|
||||
try {
|
||||
const { objectInfo: { object } } = await this.graphQLService.gql.getDBObjectInfo({
|
||||
navNodeId,
|
||||
});
|
||||
|
||||
dbObject.set(navNodeId, { navNodeId, ...object } as DBObject);
|
||||
itemMetadata.loaded = true;
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dbObject;
|
||||
return { navNodeId, ...object };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService, CachedMapResource, ResourceKey, isResourceKeyList, NavigatorNodeInfo, DatabaseObjectInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { NavNode } from './EntityTypes';
|
||||
|
||||
type NavNodeInfo = Pick<NavigatorNodeInfo, 'id' | 'name' | 'hasChildren' | 'nodeType' | 'icon' | 'folder' | 'inline' | 'navigable' | 'features'>
|
||||
& { object?: Pick<DatabaseObjectInfo, 'features'> };
|
||||
|
||||
export const ROOT_NODE_PATH = '';
|
||||
|
||||
@injectable()
|
||||
export class NavNodeInfoResource extends CachedMapResource<string, NavNode> {
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
super(new Map());
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
const values: NavNode[] = [];
|
||||
for (const nodePath of key.list) {
|
||||
values.push(await this.loadNodeInfo(nodePath));
|
||||
}
|
||||
this.set(key, values);
|
||||
} else {
|
||||
this.set(key, await this.loadNodeInfo(key));
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
navNodeInfoToNavNode(node: NavNodeInfo, parentId?: string): NavNode {
|
||||
return {
|
||||
...node,
|
||||
objectFeatures: node.object?.features || [],
|
||||
parentId: parentId || this.get(node.id)?.parentId || ROOT_NODE_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
private async loadNodeInfo(nodePath: string): Promise<NavNode> {
|
||||
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
|
||||
nodePath,
|
||||
});
|
||||
|
||||
return this.navNodeInfoToNavNode(navNodeInfo);
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,16 @@
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PermissionsService, EPermission } from '@cloudbeaver/core-root';
|
||||
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
import { GraphQLService, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { INavigator } from '../Navigation/INavigator';
|
||||
import { IContextProvider } from '../Navigation/NavigationContext';
|
||||
import { NavigationService } from '../Navigation/NavigationService';
|
||||
import { DBObjectService } from './DBObjectService';
|
||||
import { ENodeFeature } from './ENodeFeature';
|
||||
import { NavNodeInfo, NavNode } from './EntityTypes';
|
||||
import { EObjectFeature } from './EObjectFeature';
|
||||
import { NavNodeInfoResource, ROOT_NODE_PATH } from './NavNodeInfoResource';
|
||||
import { NavTreeResource } from './NavTreeResource';
|
||||
import { NodeManagerUtils } from './NodeManagerUtils';
|
||||
|
||||
export enum NavigationType {
|
||||
@@ -80,61 +80,16 @@ export interface INodeNavigationData {
|
||||
folderId?: string;
|
||||
}
|
||||
|
||||
interface INavNodeMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
interface INavTreeMetadata {
|
||||
loaded: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const ROOT_NODE_PATH = '/';
|
||||
|
||||
@injectable()
|
||||
export class NavNodeManagerService {
|
||||
readonly navNode = new CachedResource(
|
||||
new Map(),
|
||||
this.loadNavNodeInfo.bind(this),
|
||||
(_, metadata, data) => {
|
||||
if (data.nodesValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
return nodesId.every(navNodeId => metadata.get(navNodeId).loaded);
|
||||
},
|
||||
new MetadataMap<string, INavNodeMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, data) => {
|
||||
if (data.nodesValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
return nodesId.some(navNodeId => metadata.get(navNodeId).loading);
|
||||
}
|
||||
);
|
||||
readonly navTree = new CachedResource(
|
||||
new Map(),
|
||||
this.loadNavTree.bind(this),
|
||||
(_, metadata, parentId) => metadata.get(parentId).loaded,
|
||||
new MetadataMap<string, INavTreeMetadata>(() => ({ loaded: false, loading: false })),
|
||||
(_, metadata, parentId) => metadata.get(parentId).loading
|
||||
)
|
||||
readonly navigator!: INavigator<INodeNavigationData>;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private navigationService: NavigationService,
|
||||
private permissionsService: PermissionsService,
|
||||
private dbObjectService: DBObjectService,
|
||||
readonly navTree: NavTreeResource,
|
||||
readonly navNodeInfoResource: NavNodeInfoResource
|
||||
) {
|
||||
|
||||
this.navigator = this.navigationService.createNavigator<INodeNavigationData>(
|
||||
@@ -161,15 +116,16 @@ export class NavNodeManagerService {
|
||||
await this.graphQLService.gql.navRefreshNode({
|
||||
nodePath: navNodeId,
|
||||
});
|
||||
await this.navTree.refresh(true, navNodeId, false);
|
||||
this.navTree.markOutdated(resourceKeyList(this.navTree.getNestedChildren(navNodeId)));
|
||||
await this.navTree.refresh(navNodeId);
|
||||
}
|
||||
|
||||
async updateRootChildren() {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
await this.navTree.refresh(true, ROOT_NODE_PATH, true);
|
||||
this.navTree.delete(ROOT_NODE_PATH);
|
||||
return;
|
||||
}
|
||||
await this.navTree.refresh(true, ROOT_NODE_PATH, false);
|
||||
await this.navTree.refresh(ROOT_NODE_PATH);
|
||||
}
|
||||
|
||||
getTree(navNodeId: string): string[] | undefined
|
||||
@@ -188,16 +144,16 @@ export class NavNodeManagerService {
|
||||
}
|
||||
|
||||
async loadTree(navNodeId: string) {
|
||||
await this.navTree.load(navNodeId, false);
|
||||
await this.navTree.load(navNodeId);
|
||||
return this.getTree(navNodeId)!;
|
||||
}
|
||||
|
||||
async removeTree(path = ROOT_NODE_PATH) {
|
||||
await this.navTree.refresh(true, path, true);
|
||||
removeTree(path = ROOT_NODE_PATH) {
|
||||
this.navTree.delete(path);
|
||||
}
|
||||
|
||||
async refreshNode(navNodeId: string) {
|
||||
await this.navNode.refresh(true, { navNodeId: [navNodeId] });
|
||||
await this.navNodeInfoResource.refresh(navNodeId);
|
||||
}
|
||||
|
||||
getNode(navNodeId: string): NavNode | undefined
|
||||
@@ -205,41 +161,24 @@ export class NavNodeManagerService {
|
||||
getNode(navNodeKey: NavNodeKey[]): (NavNode | undefined)[]
|
||||
getNode(navNodeId: string | NavNodeKey | NavNodeKey[]) {
|
||||
if (typeof navNodeId === 'string') {
|
||||
return this.navNode.data.get(navNodeId);
|
||||
return this.navNodeInfoResource.get(navNodeId);
|
||||
}
|
||||
|
||||
if (Array.isArray(navNodeId)) {
|
||||
return navNodeId.map(node => this.navNode.data.get(node.nodeId));
|
||||
return navNodeId.map(node => this.navNodeInfoResource.get(node.nodeId));
|
||||
}
|
||||
|
||||
return this.navNode.data.get(navNodeId.nodeId);
|
||||
}
|
||||
|
||||
getNestedChildren(navNode: string | string[]) {
|
||||
const nestedChildren: string[] = [];
|
||||
let prevChildren: string[];
|
||||
if (Array.isArray(navNode)) {
|
||||
prevChildren = navNode.concat();
|
||||
nestedChildren.push(...navNode);
|
||||
} else {
|
||||
prevChildren = (this.getTree(navNode)?.concat() || []);
|
||||
nestedChildren.push(...prevChildren);
|
||||
}
|
||||
|
||||
while (prevChildren.length) {
|
||||
const nodeKey = prevChildren.shift()!;
|
||||
const children = this.getTree(nodeKey) || [];
|
||||
prevChildren.push(...children);
|
||||
nestedChildren.push(...children);
|
||||
}
|
||||
|
||||
return nestedChildren;
|
||||
return this.navNodeInfoResource.get(navNodeId.nodeId);
|
||||
}
|
||||
|
||||
async loadNode(node: NavNodeKey): Promise<NavNode>
|
||||
async loadNode(...nodes: NavNodeKey[]): Promise<NavNode>
|
||||
async loadNode(...nodes: NavNodeKey[]) {
|
||||
await this.navNode.load({ nodes });
|
||||
const items = await this.navNodeInfoResource.load(resourceKeyList(nodes.map(n => n.nodeId)));
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].parentId = nodes[i].parentId;
|
||||
}
|
||||
|
||||
if (nodes.length === 1) {
|
||||
return this.getNode(nodes[0])!;
|
||||
@@ -248,15 +187,12 @@ export class NavNodeManagerService {
|
||||
return this.getNode(nodes);
|
||||
}
|
||||
|
||||
async removeNode(navNodeId = ROOT_NODE_PATH) {
|
||||
await this.navNode.refresh(true, {
|
||||
navNodeId: [navNodeId],
|
||||
remove: true,
|
||||
});
|
||||
removeNode(navNodeId = ROOT_NODE_PATH) {
|
||||
this.navNodeInfoResource.delete(navNodeId);
|
||||
}
|
||||
|
||||
getParent(node: NavNode) {
|
||||
return this.navNode.data.get(node.parentId);
|
||||
return this.navNodeInfoResource.get(node.parentId);
|
||||
}
|
||||
|
||||
isNodeHasData(node?: string | NavNode) {
|
||||
@@ -335,7 +271,7 @@ export class NavNodeManagerService {
|
||||
const parents: string[] = [];
|
||||
let parent = this.getNode(nodeId);
|
||||
|
||||
while (parent && parent.parentId !== ROOT_NODE_PATH) {
|
||||
while (parent && parent.parentId !== ROOT_NODE_PATH && parent.id !== parent.parentId) {
|
||||
parents.unshift(parent.parentId);
|
||||
parent = this.getNode(parent.parentId);
|
||||
}
|
||||
@@ -346,15 +282,12 @@ export class NavNodeManagerService {
|
||||
const loadParents = async (parents: string[]) => {
|
||||
let parentId = ROOT_NODE_PATH;
|
||||
|
||||
await this.navNode.load({
|
||||
nodes: parents.map((nodeId) => {
|
||||
try {
|
||||
return { nodeId, parentId };
|
||||
} finally {
|
||||
parentId = nodeId;
|
||||
}
|
||||
}),
|
||||
});
|
||||
const nodes = await this.navNodeInfoResource.load(resourceKeyList(parents));
|
||||
|
||||
for (const node of nodes) {
|
||||
node.parentId = parentId;
|
||||
parentId = node.id;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -371,138 +304,4 @@ export class NavNodeManagerService {
|
||||
|
||||
private async navigateHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
}
|
||||
|
||||
private async loadNavNodeInfo(
|
||||
navNode: Map<string, NavNode>,
|
||||
metadata: MetadataMap<string, INavNodeMetadata>,
|
||||
load: boolean,
|
||||
data: INavNodePath | INavNodeData | INavNodeId,
|
||||
) {
|
||||
if (data.nodesValue) {
|
||||
for (const nodeValue of data.nodesValue) {
|
||||
const itemMetadata = metadata.get(nodeValue.node.id);
|
||||
const parentId = navNode.get(nodeValue.node.id)?.parentId || nodeValue.parentId || ROOT_NODE_PATH;
|
||||
|
||||
navNode.set(nodeValue.node.id, {
|
||||
...nodeValue.node,
|
||||
objectFeatures: nodeValue.node.object?.features || [],
|
||||
parentId,
|
||||
});
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
return navNode;
|
||||
}
|
||||
|
||||
const nodesId = data.navNodeId
|
||||
? data.navNodeId
|
||||
: data.nodes.map(node => node.nodeId);
|
||||
|
||||
if (data.remove) {
|
||||
for (const navNodeId of nodesId) {
|
||||
navNode.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
}
|
||||
|
||||
await this.dbObjectService.remove(nodesId);
|
||||
return navNode;
|
||||
}
|
||||
|
||||
for (const navNodeId of nodesId) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
|
||||
try {
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
const { navNodeInfo } = await this.graphQLService.gql.navNodeInfo({
|
||||
nodePath: navNodeId,
|
||||
});
|
||||
|
||||
let parentId = ROOT_NODE_PATH;
|
||||
if (data.navNodeId) {
|
||||
parentId = navNode.get(navNodeId)?.parentId || parentId;
|
||||
} else {
|
||||
parentId = data.nodes.find(node => node.nodeId === navNodeId)?.parentId || parentId;
|
||||
}
|
||||
|
||||
navNode.set(navNodeId, {
|
||||
...navNodeInfo,
|
||||
objectFeatures: navNodeInfo.object?.features || [],
|
||||
parentId,
|
||||
});
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (load) {
|
||||
await this.dbObjectService.dbObject.refresh(false, { navNodeId: nodesId });
|
||||
}
|
||||
|
||||
return navNode;
|
||||
}
|
||||
|
||||
private async loadNavTree(
|
||||
navTree: Map<string, string[]>,
|
||||
metadata: MetadataMap<string, INavTreeMetadata>,
|
||||
load: boolean,
|
||||
parentId: string,
|
||||
remove: boolean
|
||||
) {
|
||||
const itemMetadata = metadata.get(parentId);
|
||||
let childrenToRemove = navTree.get(parentId)?.concat() || [];
|
||||
|
||||
if (remove) {
|
||||
childrenToRemove = [parentId];
|
||||
} else {
|
||||
try {
|
||||
itemMetadata.loaded = false;
|
||||
if (load) {
|
||||
itemMetadata.loading = true;
|
||||
|
||||
const { navNodeChildren, navNodeInfo } = await this.graphQLService.gql.navNodeChildren({
|
||||
parentPath: parentId,
|
||||
});
|
||||
|
||||
await this.navNode.refresh(true, {
|
||||
nodesValue: [{ node: navNodeInfo }, ...navNodeChildren.map(node => ({ node, parentId }))],
|
||||
});
|
||||
navTree.set(parentId, navNodeChildren.map(node => node.id));
|
||||
itemMetadata.loaded = true;
|
||||
}
|
||||
} finally {
|
||||
itemMetadata.loading = false;
|
||||
}
|
||||
|
||||
const newChildren = navTree.get(parentId);
|
||||
const nestedChildren = this.getNestedChildren(parentId);
|
||||
|
||||
await this.navNode.refresh(false, {
|
||||
navNodeId: nestedChildren.filter(navNodeId => !newChildren || !newChildren.includes(navNodeId)),
|
||||
});
|
||||
|
||||
for (const navNodeId of nestedChildren) {
|
||||
const itemMetadata = metadata.get(navNodeId);
|
||||
itemMetadata.loaded = false;
|
||||
}
|
||||
|
||||
if (newChildren) {
|
||||
childrenToRemove = childrenToRemove.filter(navNodeId => !newChildren.includes(navNodeId));
|
||||
}
|
||||
}
|
||||
|
||||
const nestedChildren = this.getNestedChildren(childrenToRemove);
|
||||
await this.navNode.refresh(true, {
|
||||
navNodeId: nestedChildren.filter(navNodeId => navNodeId !== parentId),
|
||||
remove: true,
|
||||
});
|
||||
for (const navNodeId of nestedChildren) {
|
||||
navTree.delete(navNodeId);
|
||||
metadata.delete(navNodeId);
|
||||
}
|
||||
|
||||
return navTree;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedMapResource,
|
||||
ResourceKey,
|
||||
isResourceKeyList,
|
||||
ResourceKeyList,
|
||||
resourceKeyList,
|
||||
NavNodeChildrenQuery
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { NavNodeInfoResource } from './NavNodeInfoResource';
|
||||
|
||||
@injectable()
|
||||
export class NavTreeResource extends CachedMapResource<string, string[]> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private navNodeInfoResource: NavNodeInfoResource,
|
||||
) {
|
||||
super(new Map());
|
||||
this.onDataOutdated.subscribe(navNodeInfoResource.markOutdated.bind(navNodeInfoResource));
|
||||
}
|
||||
|
||||
set(key: string, value: string[]): void;
|
||||
set(key: ResourceKeyList<string>, value: string[][]): void;
|
||||
set(keyObject: ResourceKey<string>, valueObject: string[] | string[][]): void {
|
||||
if (isResourceKeyList(keyObject)) {
|
||||
for (let i = 0; i < keyObject.list.length; i++) {
|
||||
const key = keyObject.list[i];
|
||||
const value = (valueObject as string[][])[i];
|
||||
const childrenToRemove = this.data.get(key)?.concat() || [];
|
||||
this.data.set(key, value);
|
||||
this.delete(resourceKeyList(childrenToRemove.filter(navNodeId => !value.includes(navNodeId))));
|
||||
}
|
||||
} else {
|
||||
const childrenToRemove = this.data.get(keyObject)?.concat() || [];
|
||||
this.data.set(keyObject, valueObject as string[]);
|
||||
this.delete(resourceKeyList(
|
||||
childrenToRemove.filter(navNodeId => !(valueObject as string[]).includes(navNodeId))
|
||||
));
|
||||
}
|
||||
this.markUpdated(keyObject);
|
||||
this.itemAddSubject.next(keyObject);
|
||||
}
|
||||
|
||||
delete(key: string): void;
|
||||
delete(key: ResourceKeyList<string>): void;
|
||||
delete(key: ResourceKey<string>): void;
|
||||
delete(key: ResourceKey<string>) {
|
||||
const items = this.getNestedChildren(key);
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const id of items) {
|
||||
this.data.delete(id);
|
||||
}
|
||||
const allKeys = resourceKeyList(items);
|
||||
this.markUpdated(allKeys);
|
||||
this.itemDeleteSubject.next(allKeys);
|
||||
this.navNodeInfoResource.delete(resourceKeyList(items.filter(navNodeId => navNodeId !== key)));
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
const values: NavNodeChildrenQuery[] = [];
|
||||
for (const nodePath of key.list) {
|
||||
values.push(await this.loadNodeChildren(nodePath));
|
||||
}
|
||||
this.setNavObject(values);
|
||||
} else {
|
||||
this.setNavObject(await this.loadNodeChildren(key));
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
getNestedChildren(navNode: ResourceKey<string>) {
|
||||
const nestedChildren: string[] = [];
|
||||
let prevChildren: string[];
|
||||
if (isResourceKeyList(navNode)) {
|
||||
prevChildren = navNode.list.concat();
|
||||
nestedChildren.push(...navNode.list);
|
||||
} else {
|
||||
prevChildren = (this.get(navNode)?.concat() || []);
|
||||
nestedChildren.push(...prevChildren);
|
||||
}
|
||||
|
||||
while (prevChildren.length) {
|
||||
const nodeKey = prevChildren.shift()!;
|
||||
const children = this.get(nodeKey) || [];
|
||||
prevChildren.push(...children);
|
||||
nestedChildren.push(...children);
|
||||
}
|
||||
|
||||
return nestedChildren;
|
||||
}
|
||||
|
||||
private setNavObject(data: NavNodeChildrenQuery | NavNodeChildrenQuery[]) {
|
||||
if (Array.isArray(data)) {
|
||||
this.navNodeInfoResource.set(
|
||||
resourceKeyList(data.map(data => data.navNodeInfo.id)),
|
||||
data.map(data => this.navNodeInfoResource.navNodeInfoToNavNode(data.navNodeInfo)).flat()
|
||||
);
|
||||
|
||||
this.navNodeInfoResource.set(
|
||||
resourceKeyList(data.map(data => data.navNodeChildren.map(node => node.id)).flat()),
|
||||
data.map(
|
||||
data => data.navNodeChildren.map(
|
||||
node => this.navNodeInfoResource.navNodeInfoToNavNode(node, data.navNodeInfo.id)
|
||||
)
|
||||
).flat()
|
||||
);
|
||||
|
||||
this.set(
|
||||
resourceKeyList(data.map(data => data.navNodeInfo.id)),
|
||||
data.map(data => data.navNodeChildren.map(node => node.id))
|
||||
);
|
||||
} else {
|
||||
this.navNodeInfoResource.set(
|
||||
data.navNodeInfo.id,
|
||||
this.navNodeInfoResource.navNodeInfoToNavNode(data.navNodeInfo)
|
||||
);
|
||||
|
||||
this.navNodeInfoResource.set(
|
||||
resourceKeyList(data.navNodeChildren.map(node => node.id)),
|
||||
data.navNodeChildren.map(node => this.navNodeInfoResource.navNodeInfoToNavNode(node, data.navNodeInfo.id))
|
||||
);
|
||||
|
||||
this.set(data.navNodeInfo.id, data.navNodeChildren.map(node => node.id));
|
||||
}
|
||||
}
|
||||
|
||||
private async loadNodeChildren(parentPath: string) {
|
||||
const { navNodeChildren, navNodeInfo } = await this.graphQLService.gql.navNodeChildren({
|
||||
parentPath,
|
||||
});
|
||||
|
||||
return { navNodeChildren, navNodeInfo };
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import { DBObjectService } from './DBObjectService';
|
||||
|
||||
export function useDatabaseObjectInfo(navNodeId: string) {
|
||||
const dbObjectService = useService(DBObjectService);
|
||||
const dbObject = dbObjectService.getDBObject(navNodeId);
|
||||
const isLoading = dbObjectService.dbObject.isDataLoading({ navNodeId: [navNodeId] })
|
||||
const isLoaded = dbObjectService.dbObject.isLoaded({ navNodeId: [navNodeId] })
|
||||
const dbObject = dbObjectService.get(navNodeId);
|
||||
const isLoading = dbObjectService.isDataLoading(navNodeId);
|
||||
const isLoaded = dbObjectService.isLoaded(navNodeId);
|
||||
|
||||
return { dbObject, isLoading, isLoaded };
|
||||
}
|
||||
|
||||
@@ -8,19 +8,16 @@
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
import { NavNodeManagerService } from './NavNodeManagerService';
|
||||
import { NavNodeInfoResource } from './NavNodeInfoResource';
|
||||
|
||||
export function useNode(navNodeId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const node = navNodeManagerService.getNode(navNodeId);
|
||||
const isLoading = navNodeManagerService.navNode.isDataLoading({
|
||||
navNodeId: [navNodeId],
|
||||
});
|
||||
const isLoaded = navNodeManagerService.navNode.isLoaded({
|
||||
navNodeId: [navNodeId],
|
||||
});
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const node = navNodeInfoResource.get(navNodeId);
|
||||
const isLoading = navNodeInfoResource.isDataLoading(navNodeId);
|
||||
const isLoaded = navNodeInfoResource.isLoaded(navNodeId);
|
||||
const isOutdated = navNodeInfoResource.isOutdated(navNodeId);
|
||||
|
||||
return {
|
||||
node, isLoading, isLoaded,
|
||||
node, isLoading, isLoaded, isOutdated,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,17 +8,20 @@
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
import { NavNodeManagerService, ROOT_NODE_PATH } from './NodesManager/NavNodeManagerService';
|
||||
import { ROOT_NODE_PATH } from './NodesManager/NavNodeInfoResource';
|
||||
import { NavNodeManagerService } from './NodesManager/NavNodeManagerService';
|
||||
|
||||
export function useChildren(navNodeId = ROOT_NODE_PATH) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const children = navNodeManagerService.getTree(navNodeId);
|
||||
const isLoading = navNodeManagerService.navTree.isDataLoading(navNodeId, false);
|
||||
const isLoaded = navNodeManagerService.navTree.isLoaded(navNodeId, false);
|
||||
const isLoading = navNodeManagerService.navTree.isDataLoading(navNodeId);
|
||||
const isLoaded = navNodeManagerService.navTree.isLoaded(navNodeId);
|
||||
const isOutdated = navNodeManagerService.navTree.isOutdated(navNodeId);
|
||||
|
||||
return {
|
||||
children,
|
||||
isLoaded,
|
||||
isLoading,
|
||||
isOutdated,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,5 +25,6 @@ export async function bootstrap(plugins: PluginManifest[]) {
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
showErrorPage();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
import {
|
||||
ConnectionDialogsService, ConnectionsManagerService,
|
||||
MainMenuService, NavigationService, NavigationTreeService,
|
||||
NavNodeManagerService, DBObjectService, NavNodeExtensionsService,
|
||||
ConnectionSchemaManagerService, NavigationTabsService,
|
||||
NavNodeManagerService, DBObjectService, NavNodeExtensionsService, NavNodeInfoResource, NavTreeResource,
|
||||
ConnectionSchemaManagerService, ConnectionInfoResource, ContainerResource, DBDriverResource, NavigationTabsService,
|
||||
NavigationTreeContextMenuService,
|
||||
SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService,
|
||||
RouterService, ScreenService, AppScreenService, CoreSettingsService
|
||||
@@ -22,7 +22,14 @@ import { LocalizationService } from '@cloudbeaver/core-localization';
|
||||
import { PluginManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { ProductManagerService, ProductSettingsService } from '@cloudbeaver/core-product';
|
||||
import {
|
||||
SessionService, ServerService, PermissionsService, SessionSettingsService, ServerSettingsService
|
||||
SessionService,
|
||||
ServerService,
|
||||
PermissionsService,
|
||||
SessionSettingsService,
|
||||
ServerSettingsService,
|
||||
ServerConfigResource,
|
||||
PermissionsResource,
|
||||
SessionResource
|
||||
} from '@cloudbeaver/core-root';
|
||||
import { EnvironmentService, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
import { LocalStorageSaveService, SettingsService } from '@cloudbeaver/core-settings';
|
||||
@@ -39,6 +46,9 @@ export const coreManifest: PluginManifest = {
|
||||
ProductManagerService,
|
||||
PluginManagerService,
|
||||
ServerSettingsService,
|
||||
ServerConfigResource,
|
||||
PermissionsResource,
|
||||
SessionResource,
|
||||
SessionSettingsService,
|
||||
PermissionsService,
|
||||
CoreSettingsService,
|
||||
@@ -46,6 +56,9 @@ export const coreManifest: PluginManifest = {
|
||||
SessionExpireService,
|
||||
ConnectionDialogsService,
|
||||
ConnectionSchemaManagerService,
|
||||
ConnectionInfoResource,
|
||||
ContainerResource,
|
||||
DBDriverResource,
|
||||
ConnectionsManagerService,
|
||||
RouterService,
|
||||
ScreenService,
|
||||
@@ -67,6 +80,8 @@ export const coreManifest: PluginManifest = {
|
||||
NavigationTreeService,
|
||||
NavNodeManagerService,
|
||||
NavNodeExtensionsService,
|
||||
NavNodeInfoResource,
|
||||
NavTreeResource,
|
||||
DBObjectService,
|
||||
NotificationService,
|
||||
SessionService,
|
||||
|
||||
@@ -70,7 +70,7 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
|
||||
async load(): Promise<void> {
|
||||
const session = await this.sessionService.session.load();
|
||||
const session = await this.sessionService.session.load(null);
|
||||
await this.loadLocaleAsync(DEFAULT_LOCALE_NAME);
|
||||
|
||||
if (!session) {
|
||||
@@ -113,7 +113,7 @@ export class LocalizationService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async setLocale(key: string) {
|
||||
const config = await this.serverService.config.load();
|
||||
const config = await this.serverService.config.load(null);
|
||||
|
||||
if (!config) {
|
||||
throw new Error('Cant\'t get server settings');
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class PermissionsResource extends CachedDataResource<Set<string>, null> {
|
||||
@observable private loaded = false;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super(new Set());
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
has(id: string): boolean {
|
||||
return this.data.has(id);
|
||||
}
|
||||
|
||||
protected async loader(key: null): Promise<Set<string>> {
|
||||
const { permissions } = await this.graphQLService.gql.sessionPermissions();
|
||||
|
||||
this.data.clear();
|
||||
for (const permission of permissions) {
|
||||
this.data.add(permission);
|
||||
}
|
||||
this.markUpdated(key);
|
||||
this.loaded = true;
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,9 @@
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { SessionService } from './SessionService';
|
||||
|
||||
type PermissionsMetadata = {
|
||||
loaded: boolean;
|
||||
}
|
||||
import { PermissionsResource } from './PermissionsResource';
|
||||
import { SessionResource } from './SessionResource';
|
||||
|
||||
export enum EPermission {
|
||||
public = 'public'
|
||||
@@ -26,47 +22,27 @@ export class PermissionsService {
|
||||
readonly onUpdate: Observable<unknown>;
|
||||
|
||||
private updateSubject: Subject<unknown>;
|
||||
private permissions = new CachedResource(
|
||||
new Map(),
|
||||
this.refreshAsync.bind(this),
|
||||
(_, { loaded }) => loaded
|
||||
);
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private sessionService: SessionService,
|
||||
private sessionResource: SessionResource,
|
||||
private permissions: PermissionsResource,
|
||||
) {
|
||||
this.updateSubject = new Subject();
|
||||
this.onUpdate = this.updateSubject.asObservable();
|
||||
this.sessionService.onUpdate.subscribe(this.update.bind(this));
|
||||
this.sessionResource.onDataUpdate.subscribe(this.update.bind(this));
|
||||
}
|
||||
|
||||
has(id: string): boolean {
|
||||
return this.permissions.data.has(id);
|
||||
return this.permissions.has(id);
|
||||
}
|
||||
|
||||
async hasAsync(id: string): Promise<boolean> {
|
||||
const permissions = await this.permissions.load();
|
||||
return permissions.has(id);
|
||||
await this.permissions.load(null);
|
||||
return this.has(id);
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.permissions.refresh(true);
|
||||
await this.permissions.refresh(null);
|
||||
this.updateSubject.next();
|
||||
}
|
||||
|
||||
private async refreshAsync(
|
||||
data: Map<string, string>,
|
||||
metadata: PermissionsMetadata
|
||||
): Promise<Map<string, string>> {
|
||||
const { permissions } = await this.graphQLService.gql.sessionPermissions();
|
||||
|
||||
data.clear();
|
||||
for (const permission of permissions) {
|
||||
data.set(permission, permission);
|
||||
}
|
||||
metadata.loaded = true;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, CachedDataResource, ServerConfig } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class ServerConfigResource extends CachedDataResource<ServerConfig | null, null> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super(null);
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return !!this.data;
|
||||
}
|
||||
|
||||
protected async loader(key: null): Promise<ServerConfig> {
|
||||
const { serverConfig } = await this.graphQLService.gql.serverConfig();
|
||||
|
||||
this.markUpdated(key);
|
||||
return serverConfig;
|
||||
}
|
||||
}
|
||||
@@ -7,35 +7,37 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, ServerConfig, CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { parseJSONFlat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ServerConfigResource } from './ServerConfigResource';
|
||||
import { ServerSettingsService } from './ServerSettingsService';
|
||||
import { SessionService } from './SessionService';
|
||||
|
||||
@injectable()
|
||||
export class ServerService {
|
||||
readonly config = new CachedResource(undefined, this.refreshConfigAsync.bind(this), data => !!data);
|
||||
readonly settings = new ServerSettingsService(this.sessionService.settings);
|
||||
|
||||
private lastConfig: any = null
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
readonly config: ServerConfigResource,
|
||||
private sessionService: SessionService,
|
||||
) {
|
||||
|
||||
this.config.onDataUpdate.subscribe(this.refreshConfigAsync.bind(this));
|
||||
}
|
||||
|
||||
private async refreshConfigAsync(data: ServerConfig | undefined): Promise<ServerConfig> {
|
||||
const { serverConfig } = await this.graphQLService.gql.serverConfig();
|
||||
private refreshConfigAsync() {
|
||||
if (!this.config.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverConfig.productConfiguration !== this.lastConfig) {
|
||||
this.lastConfig = serverConfig.productConfiguration;
|
||||
if (this.config.data.productConfiguration !== this.lastConfig) {
|
||||
this.lastConfig = this.config.data.productConfiguration;
|
||||
this.settings.clear();
|
||||
parseJSONFlat(
|
||||
serverConfig.productConfiguration,
|
||||
this.config.data.productConfiguration,
|
||||
this.settings.setSelfValue.bind(this.settings)
|
||||
);
|
||||
}
|
||||
return serverConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
SessionInfo,
|
||||
ConnectionInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'lastAccessTime' | 'locale'> & {
|
||||
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>>;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class SessionResource extends CachedDataResource<SessionState | null, null> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super(null);
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return !!this.data;
|
||||
}
|
||||
|
||||
protected async loader(key: null): Promise<SessionState> {
|
||||
const { session } = await this.graphQLService.gql.openSession();
|
||||
|
||||
this.markUpdated(key);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -6,40 +6,21 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
ConnectionInfo, GraphQLService, SessionInfo, CachedResource,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { SessionResource } from './SessionResource';
|
||||
import { SessionSettingsService } from './SessionSettingsService';
|
||||
|
||||
export type SessionState = Pick<SessionInfo, 'createTime' | 'cacheExpired' | 'lastAccessTime' | 'locale'> & {
|
||||
connections: Array<Pick<ConnectionInfo, 'id' | 'name' | 'connected' | 'driverId' | 'features'>>;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class SessionService {
|
||||
readonly session = new CachedResource(undefined, this.refreshSessionStateAsync.bind(this), data => !!data);
|
||||
readonly settings = new SessionSettingsService('session_settings');
|
||||
readonly onUpdate: Observable<unknown>;
|
||||
|
||||
private updateSubject: Subject<unknown>;
|
||||
|
||||
constructor(private graphQLService: GraphQLService) {
|
||||
this.updateSubject = new Subject();
|
||||
this.onUpdate = this.updateSubject.asObservable();
|
||||
constructor(
|
||||
readonly session: SessionResource
|
||||
) {
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.session.refresh(true);
|
||||
}
|
||||
|
||||
private async refreshSessionStateAsync(data: SessionState | undefined): Promise<SessionState> {
|
||||
const { session } = await this.graphQLService.gql.openSession();
|
||||
|
||||
this.updateSubject.next();
|
||||
return session;
|
||||
await this.session.refresh(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export * from './SessionService';
|
||||
export * from './ServerService';
|
||||
export * from './PermissionsResource';
|
||||
export * from './PermissionsService';
|
||||
export * from './ServerConfigResource';
|
||||
export * from './SessionService';
|
||||
export * from './SessionResource';
|
||||
export * from './ServerService';
|
||||
export * from './usePermission';
|
||||
export * from './ServerSettingsService';
|
||||
export * from './SessionSettingsService';
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CachedResource } from './CachedResource';
|
||||
|
||||
@injectable()
|
||||
export abstract class CachedDataResource<
|
||||
TData,
|
||||
TParam,
|
||||
> extends CachedResource<TData, TParam> {
|
||||
|
||||
async refresh(param: TParam): Promise<TData> {
|
||||
this.markOutdated(param);
|
||||
await this.loadData(param);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async load(param: TParam): Promise<TData> {
|
||||
await this.loadData(param);
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CachedResource } from './CachedResource';
|
||||
|
||||
export const RESOURCE_KEY_LIST = Symbol('@CachedMapResource/list');
|
||||
|
||||
export type ResourceKeyList<TKey> = {
|
||||
list: TKey[];
|
||||
[RESOURCE_KEY_LIST]: true;
|
||||
}
|
||||
|
||||
export type ResourceKey<TKey> = TKey | ResourceKeyList<TKey>;
|
||||
|
||||
@injectable()
|
||||
export abstract class CachedMapResource<TKey, TValue> extends CachedResource<
|
||||
Map<TKey, TValue>,
|
||||
ResourceKey<TKey>
|
||||
>
|
||||
{
|
||||
readonly onItemAdd: Observable<ResourceKey<TKey>>;
|
||||
readonly onItemDelete: Observable<ResourceKey<TKey>>;
|
||||
|
||||
protected itemAddSubject: Subject<ResourceKey<TKey>>;
|
||||
protected itemDeleteSubject: Subject<ResourceKey<TKey>>;
|
||||
|
||||
constructor(defaultValue?: Map<TKey, TValue>) {
|
||||
super(defaultValue || new Map());
|
||||
this.itemAddSubject = new Subject();
|
||||
this.onItemAdd = this.itemAddSubject.asObservable();
|
||||
this.itemDeleteSubject = new Subject();
|
||||
this.onItemDelete = this.itemDeleteSubject.asObservable();
|
||||
}
|
||||
|
||||
isOutdated(key: ResourceKey<TKey>): boolean {
|
||||
if (isResourceKeyList(key)) {
|
||||
return key.list.some(key => this.outdated.has(key));
|
||||
}
|
||||
return this.outdated.has(key);
|
||||
}
|
||||
|
||||
markOutdated(key: ResourceKey<TKey>): void {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (const itemKey of key.list) {
|
||||
this.outdated.add(itemKey);
|
||||
}
|
||||
} else {
|
||||
this.outdated.add(key);
|
||||
}
|
||||
this.outdatedSubject.next(key);
|
||||
}
|
||||
|
||||
markUpdated(key: ResourceKey<TKey>): void {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (const itemKey of key.list) {
|
||||
this.outdated.delete(itemKey);
|
||||
}
|
||||
} else {
|
||||
this.outdated.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
isLoaded(key: ResourceKey<TKey>): boolean {
|
||||
if (isResourceKeyList(key)) {
|
||||
return key.list.every(key => this.data.has(key));
|
||||
}
|
||||
return this.data.has(key);
|
||||
}
|
||||
|
||||
isDataLoading(key: ResourceKey<TKey>): boolean {
|
||||
if (isResourceKeyList(key)) {
|
||||
return key.list.some(
|
||||
key => (isResourceKeyList(this.activePromiseParam)
|
||||
? this.activePromiseParam.list.includes(key)
|
||||
: key === this.activePromiseParam)
|
||||
);
|
||||
}
|
||||
return this.activePromiseParam === key;
|
||||
}
|
||||
|
||||
get(key: TKey): TValue | undefined;
|
||||
get(key: ResourceKeyList<TKey>): Array<TValue | undefined>;
|
||||
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined;
|
||||
get(key: ResourceKey<TKey>): Array<TValue | undefined>| TValue | undefined {
|
||||
if (isResourceKeyList(key)) {
|
||||
return key.list.map(key => this.data.get(key));
|
||||
}
|
||||
return this.data.get(key);
|
||||
}
|
||||
|
||||
set(key: TKey, value: TValue): void;
|
||||
set(key: ResourceKeyList<TKey>, value: TValue[]): void;
|
||||
set(key: ResourceKey<TKey>, value: TValue | TValue[]): void {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (let i = 0; i < key.list.length; i++) {
|
||||
this.data.set(key.list[i], (value as TValue[])[i]);
|
||||
}
|
||||
} else {
|
||||
this.data.set(key, value as TValue);
|
||||
}
|
||||
this.markUpdated(key);
|
||||
this.itemAddSubject.next(key);
|
||||
}
|
||||
|
||||
delete(key: TKey): void;
|
||||
delete(key: ResourceKeyList<TKey>): void;
|
||||
delete(key: ResourceKey<TKey>): void;
|
||||
delete(key: ResourceKey<TKey>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (let i = 0; i < key.list.length; i++) {
|
||||
this.data.delete(key.list[i]);
|
||||
}
|
||||
} else {
|
||||
this.data.delete(key);
|
||||
}
|
||||
this.markUpdated(key);
|
||||
this.itemDeleteSubject.next(key);
|
||||
}
|
||||
|
||||
async refresh(key: TKey): Promise<TValue>;
|
||||
async refresh(key: ResourceKeyList<TKey>): Promise<Array<TValue>>;
|
||||
async refresh(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue>;
|
||||
async refresh(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue> {
|
||||
this.markOutdated(key);
|
||||
await this.loadData(key);
|
||||
return this.get(key) as Array<TValue>| TValue;
|
||||
}
|
||||
|
||||
async load(key: TKey): Promise<TValue>;
|
||||
async load(key: ResourceKeyList<TKey>): Promise<Array<TValue>>;
|
||||
async load(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue>;
|
||||
async load(key: ResourceKey<TKey>): Promise<Array<TValue>| TValue> {
|
||||
await this.loadData(key);
|
||||
return this.get(key) as Array<TValue>| TValue;
|
||||
}
|
||||
}
|
||||
|
||||
export function isResourceKeyList<T>(data: any): data is ResourceKeyList<T> {
|
||||
return data && typeof data === 'object' && RESOURCE_KEY_LIST in data;
|
||||
}
|
||||
|
||||
export function resourceKeyList<T>(list: T[]): ResourceKeyList<T> {
|
||||
return { [RESOURCE_KEY_LIST]: true, list };
|
||||
}
|
||||
@@ -9,117 +9,98 @@
|
||||
import { observable } from 'mobx';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
|
||||
export type Loader<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
current: TData,
|
||||
metadata: TMetadata,
|
||||
update: boolean,
|
||||
...args: TArgs
|
||||
) => Promise<TData>
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
export type IsLoaded<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
current: TData,
|
||||
metadata: TMetadata,
|
||||
...args: TArgs
|
||||
) => boolean
|
||||
|
||||
export type IsLoading<TData, TMetadata = {}, TArgs extends any[] = []> = (
|
||||
current: TData,
|
||||
metadata: TMetadata,
|
||||
...args: TArgs
|
||||
) => boolean
|
||||
|
||||
export class CachedResource<TData, TMetadata = {}, TArgs extends any[] = []> {
|
||||
@injectable()
|
||||
export abstract class CachedResource<
|
||||
TData,
|
||||
TParam,
|
||||
> {
|
||||
@observable data: TData;
|
||||
|
||||
@observable private loading = false;
|
||||
@observable private metadata: TMetadata;
|
||||
|
||||
readonly onDataUpdate: Observable<TData>;
|
||||
private dataSubject: Subject<TData>;
|
||||
private loader: Loader<TData, TMetadata, TArgs>;
|
||||
private isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>;
|
||||
private isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>;
|
||||
private activePromise: Promise<TData> | null = null;
|
||||
readonly onDataOutdated: Observable<TParam>;
|
||||
|
||||
constructor(
|
||||
defaultValue: TData,
|
||||
loader: Loader<TData, TMetadata, TArgs>,
|
||||
isLoadedCheck: IsLoaded<TData, TMetadata, TArgs>,
|
||||
metadata?: TMetadata,
|
||||
isLoadingCheck?: IsLoading<TData, TMetadata, TArgs>,
|
||||
) {
|
||||
@observable protected outdated = new Set<TParam>();
|
||||
@observable protected loading = false;
|
||||
protected outdatedSubject: Subject<TParam>;
|
||||
protected dataSubject: Subject<TData>;
|
||||
@observable protected activePromiseParam: TParam | null = null;
|
||||
private activePromise: Promise<any> | null = null;
|
||||
|
||||
constructor(defaultValue: TData) {
|
||||
this.data = defaultValue;
|
||||
this.loader = loader;
|
||||
this.isLoadedCheck = isLoadedCheck;
|
||||
this.isLoadingCheck = isLoadingCheck;
|
||||
this.metadata = metadata || {} as TMetadata;
|
||||
this.dataSubject = new Subject<TData>();
|
||||
this.outdatedSubject = new Subject();
|
||||
this.dataSubject = new Subject();
|
||||
this.onDataOutdated = this.outdatedSubject.asObservable();
|
||||
this.onDataUpdate = this.dataSubject.asObservable();
|
||||
}
|
||||
|
||||
isLoaded(...args: TArgs): boolean {
|
||||
return this.isLoadedCheck(this.data, this.metadata, ...args);
|
||||
abstract isLoaded(param: TParam): boolean;
|
||||
|
||||
isOutdated(param: TParam): boolean {
|
||||
return this.outdated.has(param);
|
||||
}
|
||||
|
||||
isLoading(): boolean {
|
||||
return this.loading;
|
||||
}
|
||||
isDataLoading(...args: TArgs): boolean {
|
||||
if (this.isLoadingCheck) {
|
||||
return this.loading && this.isLoadingCheck(this.data, this.metadata, ...args);
|
||||
|
||||
markOutdated(param: TParam): void {
|
||||
this.outdated.add(param);
|
||||
this.outdatedSubject.next(param);
|
||||
}
|
||||
|
||||
markUpdated(param: TParam): void {
|
||||
this.outdated.delete(param);
|
||||
}
|
||||
|
||||
async refresh(param: TParam): Promise<any> {
|
||||
this.markOutdated(param);
|
||||
await this.loadData(param);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async load(param: TParam): Promise<any> {
|
||||
await this.loadData(param);
|
||||
return this.data;
|
||||
}
|
||||
|
||||
protected abstract loader(param: TParam): Promise<TData>;
|
||||
|
||||
protected async loadData(param: TParam) {
|
||||
if (this.isLoaded(param) && !this.isOutdated(param)) {
|
||||
return;
|
||||
}
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
async refresh(load = false, ...args: TArgs): Promise<TData> {
|
||||
return this.loadData(load, true, args);
|
||||
}
|
||||
|
||||
async load(...args: TArgs): Promise<TData> {
|
||||
return this.loadData(true, false, args);
|
||||
}
|
||||
|
||||
async refreshUnblocked(load = false, ...args: TArgs): Promise<TData> {
|
||||
return this.loadData(load, true, args, true);
|
||||
}
|
||||
|
||||
async loadUnblocked(...args: TArgs): Promise<TData> {
|
||||
return this.loadData(true, false, args, true);
|
||||
}
|
||||
|
||||
private async loadData(load: boolean, update: boolean, args: TArgs, unblocked?: boolean) {
|
||||
if (unblocked) {
|
||||
return this.loadingTask(load, update, args);
|
||||
}
|
||||
await this.waitActive();
|
||||
this.activePromise = this.loadingTask(load, update, args);
|
||||
await this.setActivePromise(param, this.loadingTask(param));
|
||||
this.dataSubject.next(this.data);
|
||||
}
|
||||
|
||||
protected async setActivePromise(param: TParam, promise: Promise<any>) {
|
||||
this.activePromise = promise;
|
||||
this.activePromiseParam = param;
|
||||
try {
|
||||
return await this.activePromise;
|
||||
await this.activePromise;
|
||||
} finally {
|
||||
this.activePromise = null;
|
||||
this.dataSubject.next(this.data);
|
||||
this.activePromiseParam = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async loadingTask(load: boolean, update: boolean, args: TArgs): Promise<TData> {
|
||||
private async loadingTask(param: TParam) {
|
||||
const prevState = this.loading;
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
// don't load existed data & don't refresh doesn't loaded data
|
||||
if ((this.isLoaded(...args) && !update) || (!this.isLoaded(...args) && update && !load)) {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
this.data = await this.loader(this.data, this.metadata, load, ...args);
|
||||
this.data = await this.loader(param);
|
||||
} finally {
|
||||
this.loading = prevState;
|
||||
}
|
||||
|
||||
return this.data;
|
||||
}
|
||||
|
||||
private async waitActive() {
|
||||
protected async waitActive() {
|
||||
if (this.activePromise) {
|
||||
try {
|
||||
await this.activePromise;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
export * from './CachedDataResource';
|
||||
export * from './GraphQLService';
|
||||
export * from './EnvironmentService';
|
||||
export * from './sdk';
|
||||
export * from './GQLError';
|
||||
export * from './ServerInternalError';
|
||||
export * from './CachedResource';
|
||||
export * from './CachedMapResource';
|
||||
export * from './EServerErrorCode';
|
||||
export * from './GQLErrorCatcher';
|
||||
|
||||
@@ -7,41 +7,13 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
CachedResource, GraphQLService, AdminRoleInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { RolesResource } from './RolesResource';
|
||||
|
||||
@injectable()
|
||||
export class RolesManagerService {
|
||||
readonly roles = new CachedResource(
|
||||
[],
|
||||
this.refreshAsync.bind(this),
|
||||
data => !!data.length
|
||||
)
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
readonly roles: RolesResource,
|
||||
) {
|
||||
}
|
||||
|
||||
private async refreshAsync(
|
||||
data: AdminRoleInfo[],
|
||||
_: any,
|
||||
update: boolean,
|
||||
roleId?: string
|
||||
): Promise<AdminRoleInfo[]> {
|
||||
const { roles } = await this.graphQLService.gql.getRolesList({ roleId });
|
||||
|
||||
if (!roleId) {
|
||||
return roles as AdminRoleInfo[];
|
||||
}
|
||||
|
||||
const index = data.findIndex(role => role.roleId === roleId);
|
||||
if (index !== -1) {
|
||||
data.splice(index, 1, ...roles as AdminRoleInfo[]);
|
||||
} else {
|
||||
data.push(...roles as AdminRoleInfo[]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
AdminRoleInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class RolesResource extends CachedDataResource<AdminRoleInfo[], string | undefined> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super([]);
|
||||
}
|
||||
|
||||
isLoaded(roleId?: string) {
|
||||
return roleId
|
||||
? this.data.some(role => role.roleId === roleId)
|
||||
: !!this.data.length;
|
||||
}
|
||||
|
||||
protected async loader(roleId?: string): Promise<AdminRoleInfo[]> {
|
||||
const { roles } = await this.graphQLService.gql.getRolesList({ roleId });
|
||||
|
||||
if (!roleId) {
|
||||
this.markUpdated(roleId);
|
||||
return roles as AdminRoleInfo[];
|
||||
}
|
||||
|
||||
const index = this.data.findIndex(role => role.roleId === roleId);
|
||||
if (index !== -1) {
|
||||
this.data.splice(index, 1, ...roles as AdminRoleInfo[]);
|
||||
} else {
|
||||
this.data.push(...roles as AdminRoleInfo[]);
|
||||
}
|
||||
|
||||
this.markUpdated(roleId);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -80,7 +80,7 @@ export class CreateUserDialogController implements IInitializableController, IDe
|
||||
await this.usersManagerService.grantRole(user.userId, roleId);
|
||||
}
|
||||
}
|
||||
await this.usersManagerService.users.refresh(true, user.userId);
|
||||
await this.usersManagerService.users.refresh(user.userId);
|
||||
this.notificationService.logInfo({ title: 'authentication_user_user_created' });
|
||||
this.close();
|
||||
} catch (exception) {
|
||||
@@ -113,7 +113,7 @@ export class CreateUserDialogController implements IInitializableController, IDe
|
||||
|
||||
private async loadRoles() {
|
||||
try {
|
||||
await this.rolesManagerService.roles.load();
|
||||
await this.rolesManagerService.roles.load(undefined);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load roles');
|
||||
}
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ export class UsersAdministrationController {
|
||||
|
||||
update = async () => {
|
||||
try {
|
||||
await this.usersManagerService.users.refresh(true, undefined);
|
||||
await this.usersManagerService.users.refresh(undefined);
|
||||
} catch (exception) {
|
||||
if (!this.error.catch(exception)) {
|
||||
this.notificationService.logException(exception, 'Users update failed');
|
||||
@@ -78,7 +78,7 @@ export class UsersAdministrationController {
|
||||
await this.usersManagerService.delete(userId);
|
||||
}
|
||||
this.selectedItems.clear();
|
||||
await this.usersManagerService.users.refresh(true, undefined);
|
||||
await this.usersManagerService.users.refresh(undefined);
|
||||
} catch (exception) {
|
||||
if (!this.error.catch(exception)) {
|
||||
this.notificationService.logException(exception, 'User delete failed');
|
||||
|
||||
@@ -7,22 +7,19 @@
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CachedResource, GraphQLService, AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
import { GraphQLService, AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { AuthInfoService } from '../AuthInfoService';
|
||||
import { AuthProviderService } from '../AuthProviderService';
|
||||
import { UsersResource } from './UsersResource';
|
||||
|
||||
@injectable()
|
||||
export class UsersManagerService {
|
||||
readonly users = new CachedResource(
|
||||
[],
|
||||
this.refreshAsync.bind(this),
|
||||
(data, _, userId) => (userId ? data.some(user => user.userId === userId) : !!data.length)
|
||||
)
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private authProviderService: AuthProviderService,
|
||||
private authInfoService: AuthInfoService,
|
||||
readonly users: UsersResource
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -39,7 +36,7 @@ export class UsersManagerService {
|
||||
async grantRole(userId: string, roleId: string, update?: boolean) {
|
||||
await this.graphQLService.gql.grantUserRole({ userId, roleId });
|
||||
if (update) {
|
||||
await this.users.refresh(true, userId);
|
||||
await this.users.refresh(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +47,7 @@ export class UsersManagerService {
|
||||
await this.graphQLService.gql.deleteUser({ userId });
|
||||
|
||||
if (update) {
|
||||
await this.users.refresh(true, userId);
|
||||
await this.users.refresh(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,26 +61,4 @@ export class UsersManagerService {
|
||||
credentials: processedCredentials,
|
||||
});
|
||||
}
|
||||
|
||||
private async refreshAsync(
|
||||
data: AdminUserInfo[],
|
||||
_: any,
|
||||
update: boolean,
|
||||
userId?: string
|
||||
): Promise<AdminUserInfo[]> {
|
||||
const { users } = await this.graphQLService.gql.getUsersList({ userId });
|
||||
|
||||
if (!userId) {
|
||||
return users as AdminUserInfo[];
|
||||
}
|
||||
|
||||
const index = data.findIndex(user => user.userId === userId);
|
||||
if (index !== -1) {
|
||||
data.splice(index, 1, ...users as AdminUserInfo[]);
|
||||
} else {
|
||||
data.push(...users as AdminUserInfo[]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
AdminUserInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class UsersResource extends CachedDataResource<AdminUserInfo[], string | undefined> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super([]);
|
||||
}
|
||||
|
||||
isLoaded(userId?: string) {
|
||||
return userId
|
||||
? this.data.some(role => role.userId === userId)
|
||||
: !!this.data.length;
|
||||
}
|
||||
|
||||
protected async loader(userId?: string): Promise<AdminUserInfo[]> {
|
||||
const { users } = await this.graphQLService.gql.getUsersList({ userId });
|
||||
|
||||
if (!userId) {
|
||||
this.markUpdated(userId);
|
||||
return users as AdminUserInfo[];
|
||||
}
|
||||
|
||||
const index = this.data.findIndex(user => user.userId === userId);
|
||||
if (index !== -1) {
|
||||
this.data.splice(index, 1, ...users as AdminUserInfo[]);
|
||||
} else {
|
||||
this.data.push(...users as AdminUserInfo[]);
|
||||
}
|
||||
|
||||
this.markUpdated(userId);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -9,22 +9,18 @@
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService, CachedResource, AuthProviderInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>
|
||||
import { AuthProvidersResource } from './AuthProvidersResource';
|
||||
|
||||
@injectable()
|
||||
export class AuthProviderService {
|
||||
readonly providers = new CachedResource([], this.refreshAsync.bind(this), data => !!data.length)
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private providers: AuthProvidersResource,
|
||||
) { }
|
||||
|
||||
async processCredentials(providerId: string, credentials: Record<string, any>) {
|
||||
const providers = await this.providers.load();
|
||||
const providers = await this.providers.load(null);
|
||||
const provider = providers.find(provider => provider.id === providerId);
|
||||
|
||||
if (!provider) {
|
||||
@@ -44,10 +40,4 @@ export class AuthProviderService {
|
||||
|
||||
return credentialsProcessed;
|
||||
}
|
||||
|
||||
private async refreshAsync(data: AuthProvider[]): Promise<AuthProvider[]> {
|
||||
const { providers } = await this.graphQLService.gql.getAuthProviders();
|
||||
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import {
|
||||
GraphQLService,
|
||||
CachedDataResource,
|
||||
AuthProviderInfo
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
export type AuthProvider = Omit<AuthProviderInfo, 'configurationParameters'>
|
||||
|
||||
@injectable()
|
||||
export class AuthProvidersResource extends CachedDataResource<AuthProvider[], null> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super([]);
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return !!this.data.length;
|
||||
}
|
||||
|
||||
protected async loader(key: null): Promise<AuthProvider[]> {
|
||||
const { providers } = await this.graphQLService.gql.getAuthProviders();
|
||||
|
||||
this.markUpdated(key);
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export class AuthenticationService {
|
||||
) { }
|
||||
|
||||
async auth() {
|
||||
const config = await this.serverService.config.load();
|
||||
const config = await this.serverService.config.load(null);
|
||||
if (!config) {
|
||||
throw new Error('Can\'t configure Authentication');
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GQLErrorCatcher } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { AuthInfoService } from '../AuthInfoService';
|
||||
import { AuthProviderService, AuthProvider } from '../AuthProviderService';
|
||||
import { AuthProvidersResource, AuthProvider } from '../AuthProvidersResource';
|
||||
|
||||
@injectable()
|
||||
export class AuthDialogController implements IInitializableController, IDestructibleController {
|
||||
@@ -24,12 +24,11 @@ export class AuthDialogController implements IInitializableController, IDestruct
|
||||
@observable credentials = {};
|
||||
|
||||
get isLoading() {
|
||||
return this.authProviderService.providers.isLoading();
|
||||
return this.authProvidersResource.isLoading();
|
||||
}
|
||||
|
||||
@computed get providers(): AuthProvider[] {
|
||||
return this.authProviderService
|
||||
.providers
|
||||
return this.authProvidersResource
|
||||
.data
|
||||
.concat()
|
||||
.sort(this.compareProviders);
|
||||
@@ -41,7 +40,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
private authProviderService: AuthProviderService,
|
||||
private authProvidersResource: AuthProvidersResource,
|
||||
private authInfoService: AuthInfoService,
|
||||
private commonDialogService: CommonDialogService,
|
||||
) { }
|
||||
@@ -77,8 +76,8 @@ export class AuthDialogController implements IInitializableController, IDestruct
|
||||
if (providerId === this.provider?.id) {
|
||||
return;
|
||||
}
|
||||
this.provider = this.authProviderService
|
||||
.providers.data.find(provider => provider.id === providerId) || null;
|
||||
this.provider = this.authProvidersResource
|
||||
.data.find(provider => provider.id === providerId) || null;
|
||||
this.credentials = {};
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ export class AuthDialogController implements IInitializableController, IDestruct
|
||||
|
||||
private async loadProviders() {
|
||||
try {
|
||||
await this.authProviderService.providers.load();
|
||||
await this.authProvidersResource.load(null);
|
||||
if (this.providers.length > 0) {
|
||||
this.provider = this.providers[0];
|
||||
}
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import styled from 'reshadow';
|
||||
import { InputField, InFocus } from '@cloudbeaver/core-blocks';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { AuthProvider } from '../../AuthProviderService';
|
||||
import { AuthProvider } from '../../AuthProvidersResource';
|
||||
import { formStyles } from './formStyles';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -5,4 +5,5 @@ export default manifest;
|
||||
export * from './AuthenticationService';
|
||||
export * from './AuthInfoService';
|
||||
export * from './AuthProviderService';
|
||||
export * from './AuthProvidersResource';
|
||||
export * from './Dialog/AuthDialogService';
|
||||
|
||||
@@ -9,13 +9,16 @@
|
||||
import { IServiceInjector, PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { RolesManagerService } from './Administration/RolesManagerService';
|
||||
import { RolesResource } from './Administration/RolesResource';
|
||||
import { UsersAdministrationService } from './Administration/Users/UsersAdministrationService';
|
||||
import { UsersManagerService } from './Administration/UsersManagerService';
|
||||
import { UsersResource } from './Administration/UsersResource';
|
||||
import { AuthenticationLocaleService } from './AuthenticationLocaleService';
|
||||
import { AuthenticationService } from './AuthenticationService';
|
||||
import { AuthInfoService } from './AuthInfoService';
|
||||
import { AuthMenuService } from './AuthMenuService';
|
||||
import { AuthProviderService } from './AuthProviderService';
|
||||
import { AuthProvidersResource } from './AuthProvidersResource';
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { AuthDialogService } from './Dialog/AuthDialogService';
|
||||
|
||||
@@ -28,12 +31,15 @@ export const manifest: PluginManifest = {
|
||||
AuthenticationService,
|
||||
AuthInfoService,
|
||||
AuthProviderService,
|
||||
AuthProvidersResource,
|
||||
AuthDialogService,
|
||||
AuthMenuService,
|
||||
UsersAdministrationService,
|
||||
UsersManagerService,
|
||||
RolesManagerService,
|
||||
AuthenticationLocaleService,
|
||||
RolesResource,
|
||||
UsersResource,
|
||||
],
|
||||
|
||||
async initialize(services: IServiceInjector) {
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ import { MainMenuService, ConnectionDialogsService } from '@cloudbeaver/core-app
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { PermissionsService, EPermission, SessionService } from '@cloudbeaver/core-root';
|
||||
import { PermissionsService, EPermission, SessionResource } from '@cloudbeaver/core-root';
|
||||
|
||||
import { BasicConnectionService } from './BasicConnectionService';
|
||||
import { ConnectionDialog } from './ConnectionDialog/ConnectionDialog';
|
||||
@@ -25,13 +25,13 @@ export class BasicConnectionPluginBootstrap {
|
||||
private commonDialogService: CommonDialogService,
|
||||
private notificationService: NotificationService,
|
||||
private permissionsService: PermissionsService,
|
||||
private sessionService: SessionService
|
||||
private sessionResource: SessionResource
|
||||
) {
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
this.loadDbSources();
|
||||
this.sessionService.onUpdate.subscribe(this.loadDbSources.bind(this));
|
||||
this.sessionResource.onDataUpdate.subscribe(this.loadDbSources.bind(this));
|
||||
this.mainMenuService.registerMenuItem(
|
||||
this.connectionDialogsService.newConnectionMenuToken,
|
||||
{
|
||||
@@ -51,7 +51,7 @@ export class BasicConnectionPluginBootstrap {
|
||||
|
||||
private async loadDbSources() {
|
||||
try {
|
||||
await this.basicConnectionService.dbSources.refresh(true);
|
||||
await this.basicConnectionService.dbSources.refresh(null);
|
||||
} catch (error) {
|
||||
this.notificationService.logException(error, 'DBSources loading failed');
|
||||
}
|
||||
|
||||
@@ -10,33 +10,27 @@ import {
|
||||
Connection, DBSource, ConnectionsManagerService
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { PermissionsService, EPermission } from '@cloudbeaver/core-root';
|
||||
import { ConnectionConfig, GraphQLService, CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { PermissionsService } from '@cloudbeaver/core-root';
|
||||
import { ConnectionConfig, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DataSourcesResource } from './DataSourcesResource';
|
||||
|
||||
@injectable()
|
||||
export class BasicConnectionService {
|
||||
readonly dbSources = new CachedResource([], this.loadDBSourcesAsync.bind(this), data => !!data.length)
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private permissionsService: PermissionsService
|
||||
private permissionsService: PermissionsService,
|
||||
readonly dbSources: DataSourcesResource
|
||||
) {
|
||||
this.permissionsService.onUpdate.subscribe(() => this.dbSources.refresh());
|
||||
this.permissionsService.onUpdate.subscribe(() => this.dbSources.refresh(null));
|
||||
}
|
||||
|
||||
public getDBSources(): DBSource[] {
|
||||
return this.dbSources.data;
|
||||
}
|
||||
|
||||
private async loadDBSourcesAsync(data: DBSource[]): Promise<DBSource[]> {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
return [];
|
||||
}
|
||||
const { dataSourceList } = await this.graphQLService.gql.dataSourceList();
|
||||
return dataSourceList;
|
||||
}
|
||||
|
||||
async openConnectionAsync(config: ConnectionConfig): Promise<Connection> {
|
||||
const response = await this.graphQLService.gql.openConnection({ config });
|
||||
this.connectionsManagerService.addOpenedConnection(response.openConnection);
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ implements IInitializableController, IDestructibleController, IConnectionControl
|
||||
|
||||
private async loadDBSources() {
|
||||
try {
|
||||
await this.basicConnectionService.dbSources.load();
|
||||
await this.basicConnectionService.dbSources.load(null);
|
||||
await this.connectionsManagerService.loadDriversAsync();
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load database sources');
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { DBSource } from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { EPermission, PermissionsService } from '@cloudbeaver/core-root';
|
||||
import { GraphQLService, CachedDataResource } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class DataSourcesResource extends CachedDataResource<DBSource[], null> {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private permissionsService: PermissionsService
|
||||
) {
|
||||
super([]);
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return !!this.data.length;
|
||||
}
|
||||
|
||||
protected async loader(key: null): Promise<DBSource[]> {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
this.markUpdated(key);
|
||||
return [];
|
||||
}
|
||||
const { dataSourceList } = await this.graphQLService.gql.dataSourceList();
|
||||
this.markUpdated(key);
|
||||
return dataSourceList;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { BasicConnectionPluginBootstrap } from './BasicConnectionPluginBootstrap';
|
||||
import { BasicConnectionService } from './BasicConnectionService';
|
||||
import { DataSourcesResource } from './DataSourcesResource';
|
||||
import { LocaleService } from './LocaleService';
|
||||
|
||||
export const basicConnectionPluginManifest: PluginManifest = {
|
||||
@@ -19,6 +20,7 @@ export const basicConnectionPluginManifest: PluginManifest = {
|
||||
|
||||
providers: [
|
||||
BasicConnectionService,
|
||||
DataSourcesResource,
|
||||
LocaleService,
|
||||
],
|
||||
|
||||
|
||||
@@ -8,30 +8,19 @@
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import {
|
||||
CachedResource, GraphQLService, DataTransferProcessorInfo, DataTransferParameters
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
import { DataTransferParameters } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DataExportProcessService } from './DataExportProcessService';
|
||||
import { DataTransferProcessorsResource } from './DataTransferProcessorsResource';
|
||||
import { ExportNotification } from './ExportNotification/ExportNotification';
|
||||
import { IExportContext } from './IExportContext';
|
||||
|
||||
type ProcessorsResourceMetadata = {
|
||||
loaded: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class DataExportService {
|
||||
readonly processors = new CachedResource(
|
||||
new Map(),
|
||||
this.refreshProcessorsAsync.bind(this),
|
||||
(_, { loaded }) => loaded
|
||||
);
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private notificationService: NotificationService,
|
||||
private dataExportProcessService: DataExportProcessService,
|
||||
readonly processors: DataTransferProcessorsResource
|
||||
) { }
|
||||
|
||||
async cancel(exportId: string) {
|
||||
@@ -62,19 +51,4 @@ export class DataExportService {
|
||||
this.notificationService.customNotification(() => ExportNotification, taskId);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
private async refreshProcessorsAsync(
|
||||
data: Map<string, DataTransferProcessorInfo>,
|
||||
metadata: ProcessorsResourceMetadata,
|
||||
): Promise<Map<string, DataTransferProcessorInfo>> {
|
||||
const { processors } = await this.graphQLService.gql.getDataTransferProcessors();
|
||||
|
||||
data.clear();
|
||||
|
||||
for (const processor of processors) {
|
||||
data.set(processor.id, processor);
|
||||
}
|
||||
metadata.loaded = true;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { GraphQLService, CachedDataResource, DataTransferProcessorInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class DataTransferProcessorsResource extends CachedDataResource<Map<string, DataTransferProcessorInfo>, null> {
|
||||
@observable private loaded = false;
|
||||
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
) {
|
||||
super(new Map());
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
protected async loader(key: null) {
|
||||
const { processors } = await this.graphQLService.gql.getDataTransferProcessors();
|
||||
|
||||
this.data.clear();
|
||||
|
||||
for (const processor of processors) {
|
||||
this.data.set(processor.id, processor);
|
||||
}
|
||||
this.loaded = true;
|
||||
this.markUpdated(key);
|
||||
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export class DataExportController implements IInitializableController, IDestruct
|
||||
|
||||
private async loadProcessors() {
|
||||
try {
|
||||
await this.dataExportService.processors.load();
|
||||
await this.dataExportService.processors.load(null);
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load data export processors');
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Bootstrap } from './Bootstrap';
|
||||
import { DataExportMenuService } from './DataExportMenuService';
|
||||
import { DataExportProcessService } from './DataExportProcessService';
|
||||
import { DataExportService } from './DataExportService';
|
||||
import { DataTransferProcessorsResource } from './DataTransferProcessorsResource';
|
||||
|
||||
export const manifest: PluginManifest = {
|
||||
info: {
|
||||
@@ -22,6 +23,7 @@ export const manifest: PluginManifest = {
|
||||
DataExportMenuService,
|
||||
DataExportService,
|
||||
DataExportProcessService,
|
||||
DataTransferProcessorsResource,
|
||||
],
|
||||
|
||||
async initialize(services: IServiceInjector) {
|
||||
|
||||
@@ -199,7 +199,7 @@ export class TableViewerModel {
|
||||
}
|
||||
|
||||
updateRows(rows: number[]) {
|
||||
this.agGridModel.actions.updateRows(rows);
|
||||
this.agGridModel.actions?.updateRows(rows);
|
||||
}
|
||||
|
||||
revertCellValue(rowNumber: number, column: string) {
|
||||
|
||||
+8
-3
@@ -9,7 +9,7 @@
|
||||
import { autorun } from 'mobx';
|
||||
import { IReactionDisposer } from 'mobx/lib/internal';
|
||||
|
||||
import { ITab } from '@cloudbeaver/core-app';
|
||||
import { ITab, NavigationTabsService } from '@cloudbeaver/core-app';
|
||||
import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di';
|
||||
|
||||
import { IObjectViewerTabState } from '../IObjectViewerTabState';
|
||||
@@ -23,7 +23,10 @@ export class ObjectFoldersController implements IInitializableController, IDestr
|
||||
private navigationTab!: ITab<IObjectViewerTabState>
|
||||
private disposer!: IReactionDisposer;
|
||||
|
||||
constructor(private objectFoldersService: ObjectFoldersService) { }
|
||||
constructor(
|
||||
private objectFoldersService: ObjectFoldersService,
|
||||
private navigationTabsService: NavigationTabsService
|
||||
) { }
|
||||
|
||||
getTabContainer() {
|
||||
return this.tabContainer;
|
||||
@@ -36,7 +39,9 @@ export class ObjectFoldersController implements IInitializableController, IDestr
|
||||
|
||||
this.disposer = autorun(() => {
|
||||
const currentFolderId = tab.handlerState.folderId || null;
|
||||
this.activateTab(currentFolderId);
|
||||
if (this.navigationTabsService.currentTabId === tab.id) {
|
||||
this.activateTab(currentFolderId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ResourceKey, resourceKeyList, isResourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { IObjectViewerTabContext } from './IObjectViewerTabContext';
|
||||
import { IObjectViewerTabState } from './IObjectViewerTabState';
|
||||
@@ -63,7 +64,8 @@ export class ObjectViewerTabService {
|
||||
|
||||
registerTabHandler() {
|
||||
this.navNodeManagerService.navigator.addHandler(this.navigationHandler.bind(this));
|
||||
this.navNodeManagerService.navNode.onDataUpdate.subscribe(this.updateTabs.bind(this));
|
||||
this.navNodeManagerService.navNodeInfoResource.onItemAdd.subscribe(this.updateTabs.bind(this));
|
||||
this.navNodeManagerService.navNodeInfoResource.onItemDelete.subscribe(this.removeTabs.bind(this));
|
||||
}
|
||||
|
||||
objectViewerTabContext = async (
|
||||
@@ -131,22 +133,47 @@ export class ObjectViewerTabService {
|
||||
};
|
||||
}
|
||||
|
||||
private async updateTabs(data: Map<string, NavNode>) {
|
||||
for (const tab of this.navigationTabsService.findTabs(isObjectViewerTab(tab => tab.restored))) {
|
||||
if (!data.has(tab.handlerState.objectId)) {
|
||||
await this.navigationTabsService.closeTab(tab.id, true);
|
||||
} else if (tab.id === this.navigationTabsService.currentTabId) {
|
||||
const loaded = this.navNodeManagerService.navNode.isLoaded({
|
||||
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
|
||||
});
|
||||
const loading = this.navNodeManagerService.navNode.isDataLoading({
|
||||
nodes: [{ nodeId: tab.handlerState.objectId, parentId: tab.handlerState.parentId }],
|
||||
});
|
||||
private async updateTabs(key: ResourceKey<string>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (const objectId of key.list) {
|
||||
const tab = this.navigationTabsService.findTab(
|
||||
isObjectViewerTab(tab => tab.handlerState.objectId === objectId)
|
||||
);
|
||||
|
||||
if (!loaded && !loading) {
|
||||
if (tab && tab.restored && this.navigationTabsService.currentTabId === tab.id) {
|
||||
await this.navigationTabsService.selectTab(tab.id);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const tab = this.navigationTabsService.findTab(
|
||||
isObjectViewerTab(tab => tab.handlerState.objectId === key)
|
||||
);
|
||||
|
||||
if (tab && tab.restored && this.navigationTabsService.currentTabId === tab.id) {
|
||||
await this.navigationTabsService.selectTab(tab.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async removeTabs(key: ResourceKey<string>) {
|
||||
if (isResourceKeyList(key)) {
|
||||
for (const objectId of key.list) {
|
||||
const tab = this.navigationTabsService.findTab(
|
||||
isObjectViewerTab(tab => tab.handlerState.objectId === objectId)
|
||||
);
|
||||
|
||||
if (tab) {
|
||||
await this.navigationTabsService.closeTab(tab.id, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const tab = this.navigationTabsService.findTab(
|
||||
isObjectViewerTab(tab => tab.handlerState.objectId === key)
|
||||
);
|
||||
|
||||
if (tab) {
|
||||
await this.navigationTabsService.closeTab(tab.id, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +230,7 @@ export class ObjectViewerTabService {
|
||||
}
|
||||
const folderChildren = await this.navNodeManagerService.loadTree(folderId);
|
||||
|
||||
await this.dbObjectService.load(folderChildren, folderId);
|
||||
await this.dbObjectService.loadChildren(folderId, resourceKeyList(folderChildren));
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while tab selecting');
|
||||
}
|
||||
@@ -244,15 +271,6 @@ export class ObjectViewerTabService {
|
||||
try {
|
||||
const { tab, tabInfo, nodeInfo } = await contexts.getContext(this.objectViewerTabContext);
|
||||
|
||||
if (nodeInfo.type === NavigationType.closeConnection) {
|
||||
for (const tab of this.navigationTabsService.findTabs(
|
||||
isObjectViewerTab(tab => tab.handlerState.objectId.includes(nodeInfo.nodeId))
|
||||
)) {
|
||||
await this.navigationTabsService.closeTab(tab.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (tab) {
|
||||
if (!tab.handlerState.folderId || (nodeInfo.folderId && tab.handlerState.folderId !== nodeInfo.folderId)) {
|
||||
tab.handlerState.folderId = nodeInfo.folderId;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { NavNodeManagerService, DBObjectService } from '@cloudbeaver/core-app';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export function useObjectFolder(objectId: string) {
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
@@ -15,8 +16,8 @@ export function useObjectFolder(objectId: string) {
|
||||
|
||||
const children = navNodeManagerService.getTree(objectId) || [];
|
||||
|
||||
const isLoading = children.some(navNodeId => !dbObjectService.getDBObject(navNodeId))
|
||||
&& dbObjectService.dbObject.isDataLoading({ navNodeId: children });
|
||||
const isLoading = !dbObjectService.isLoaded(resourceKeyList(children))
|
||||
&& dbObjectService.isDataLoading(resourceKeyList(children));
|
||||
|
||||
return { isLoading };
|
||||
}
|
||||
|
||||
@@ -39,11 +39,6 @@ export class SqlEditorBootstrap {
|
||||
title: 'SQL',
|
||||
order: 2,
|
||||
onClick: () => {
|
||||
console.log(
|
||||
this.connectionSchemaManagerService.currentConnectionId,
|
||||
this.connectionSchemaManagerService.currentObjectCatalogId,
|
||||
this.connectionSchemaManagerService.currentObjectSchemaId
|
||||
);
|
||||
this.sqlEditorNavigatorService.openNewEditor(
|
||||
this.connectionSchemaManagerService.currentConnectionId,
|
||||
this.connectionSchemaManagerService.currentObjectCatalogId,
|
||||
|
||||
@@ -14,9 +14,6 @@ import {
|
||||
NavigationService,
|
||||
IContextProvider,
|
||||
ITabOptions,
|
||||
NavNodeManagerService,
|
||||
INodeNavigationData,
|
||||
NavigationType,
|
||||
} from '@cloudbeaver/core-app';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
@@ -63,7 +60,6 @@ export class SqlEditorNavigatorService {
|
||||
private notificationService: NotificationService,
|
||||
private gql: GraphQLService,
|
||||
private sqlDialectInfoService: SqlDialectInfoService,
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private navigationService: NavigationService
|
||||
) {
|
||||
|
||||
@@ -71,7 +67,7 @@ export class SqlEditorNavigatorService {
|
||||
null,
|
||||
this.navigateHandler.bind(this)
|
||||
);
|
||||
this.navNodeManagerService.navigator.addHandler(this.nodeNavigationHandler.bind(this));
|
||||
this.connectionsManagerService.onCloseConnection.subscribe(this.nodeNavigationHandler.bind(this));
|
||||
}
|
||||
|
||||
registerTabHandler() {
|
||||
@@ -102,18 +98,14 @@ export class SqlEditorNavigatorService {
|
||||
});
|
||||
}
|
||||
|
||||
private async nodeNavigationHandler(contexts: IContextProvider<INodeNavigationData>) {
|
||||
private async nodeNavigationHandler(connectionId: string) {
|
||||
try {
|
||||
const nodeInfo = await contexts.getContext(this.navNodeManagerService.navigationNavNodeContext);
|
||||
|
||||
if (nodeInfo.type === NavigationType.closeConnection) {
|
||||
for (const tab of this.navigationTabsService.findTabs(
|
||||
isSQLEditorTab(tab => tab.handlerState.connectionId.includes(nodeInfo.nodeId))
|
||||
)) {
|
||||
await this.navigationTabsService.closeTab(tab.id);
|
||||
}
|
||||
return;
|
||||
for (const tab of this.navigationTabsService.findTabs(
|
||||
isSQLEditorTab(tab => tab.handlerState.connectionId.includes(connectionId))
|
||||
)) {
|
||||
await this.navigationTabsService.closeTab(tab.id);
|
||||
}
|
||||
return;
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Error in Object Viewer while processing action with database node');
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export class SqlEditorTabService {
|
||||
try {
|
||||
await this.gql.gql.sqlContextDestroy({ connectionId, contextId });
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`);
|
||||
this.notificationService.logException(exception, `Failed to destroy SQL-context ${contextId}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user