import { Connection , ConnectionInfoResource , ConnectionsManagerService } from '@cloudbeaver/core-connections' ; import { injectable , Bootstrap } from '@cloudbeaver/core-di' ; import { NotificationService } from '@cloudbeaver/core-events' ; import { IExecutor , Executor , IExecutionContextProvider } from '@cloudbeaver/core-executor' ; import { PermissionsService , EPermission , ServerService } from '@cloudbeaver/core-root' ; import { resourceKeyList } from '@cloudbeaver/core-sdk' ; import { NavigationService } from '@cloudbeaver/core-ui' ; import { ENodeFeature } from './ENodeFeature' ; import type { 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 { open } export interface NavNodeKey { nodeId : string ; parentId : string ; } export interface NavNodeValue { node : NavNodeInfo ; parentId ? : string ; } export interface INodeContainerInfo { connectionId ? : string ; catalogId ? : string ; schemaId ? : string ; } export interface INavNodePath { nodes : NavNodeKey [ ] ; remove ? : boolean ; navNodeId ? : never ; nodesValue ? : never ; } export interface INavNodeData { nodesValue : NavNodeValue [ ] ; navNodeId ? : never ; nodes ? : never ; remove ? : never ; } export interface INavNodeId { navNodeId : string [ ] ; remove ? : boolean ; nodes ? : never ; nodesValue ? : never ; } export interface INodeNavigationContext { type : NavigationType ; connection : Connection | undefined ; nodeId : string ; parentId : string ; folderId : string ; name ? : string ; icon ? : string ; getParents : ( ) => string [ ] ; loadParents : ( parents : string [ ] ) => Promise < void > ; } export interface INodeNavigationData { type : NavigationType ; nodeId : string ; parentId : string ; folderId ? : string ; } @ injectable ( ) export class NavNodeManagerService extends Bootstrap { readonly navigator : IExecutor < INodeNavigationData > ; constructor ( private permissionsService , readonly connectionInfo , readonly navTree , readonly navNodeInfoResource , private connectionsManagerService , private notificationService , private serverService , navigationService ) { super ( ) ; this . navigator = new Executor ( { type : NavigationType . open , nodeId : ROOT_NODE_PATH , parentId : ROOT_NODE_PATH , } , ( active , current ) => active . nodeId === current . nodeId ) . before ( navigationService . navigationTask ) . addHandler ( this . navigateHandler . bind ( this ) ) ; } register ( ) { } load ( ) { } async navToNode ( nodeId , parentId , folderId ? ) { await this . navigator . execute ( { type : NavigationType . open , nodeId , parentId , folderId , } ) ; } async refreshTree ( navNodeId ) { await this . navTree . refreshTree ( navNodeId ) ; } getTree ( navNodeId ) ; getTree ( navNodeKey ) ; getTree ( navNodeKey ) ; getTree ( navNodeId ) { if ( typeof navNodeId === 'string' ) { return this . navTree . get ( navNodeId ) ; } if ( Array . isArray ( navNodeId ) ) { return navNodeId . map ( node => this . navTree . data . get ( node . nodeId ) ) ; } return this . navTree . get ( navNodeId . nodeId ) ; } loadTree ( navNodeId ) { return this . navTree . load ( navNodeId ) ; } removeTree ( path = ROOT_NODE_PATH ) { this . navTree . delete ( path ) ; } getNode ( navNodeId ) ; getNode ( navNodeKey ) ; getNode ( navNodeKey ) ; getNode ( navNodeId ) { if ( typeof navNodeId === 'string' ) { return this . navNodeInfoResource . get ( navNodeId ) ; } if ( Array . isArray ( navNodeId ) ) { return navNodeId . map ( node => this . navNodeInfoResource . get ( node . nodeId ) ) ; } return this . navNodeInfoResource . get ( navNodeId . nodeId ) ; } async loadNode ( node ) ; async loadNode ( ... nodes ) ; async loadNode ( ... 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 ] ) ! ; } return this . getNode ( nodes ) as NavNode [ ] ; } removeNode ( navNodeId = ROOT_NODE_PATH ) { this . navNodeInfoResource . delete ( navNodeId ) ; } getParent ( node ) { return this . navNodeInfoResource . get ( node . parentId ) ; } isNodeHasData ( node ? ) { if ( typeof node === 'string' ) { node = this . getNode ( node ) ; } if ( ! node || ! node . objectFeatures ) { return false ; } return node . objectFeatures . includes ( ENodeFeature . dataContainer ) || node . objectFeatures . includes ( ENodeFeature . container ) ; } getNodeContainerInfo ( nodeId ) { const initial = { } ; const scanParents = ( res , nodeId ? ) : INodeContainerInfo => { if ( ! nodeId ) { return res ; } const object = this . getNode ( nodeId ) ; if ( ! object ) { return res ; } if ( object . objectFeatures . includes ( EObjectFeature . dataSource ) ) { res . connectionId = object . id ; } if ( object . objectFeatures . includes ( EObjectFeature . catalog ) ) { res . catalogId = object . name ; } if ( object . objectFeatures . includes ( EObjectFeature . schema ) ) { res . schemaId = object . name ; } return scanParents ( res , object . parentId ) ; } ; return scanParents ( initial , nodeId ) ; } navigationNavNodeContext = async ( contexts , data ) : Promise < INodeNavigationContext > => { let nodeId = data . nodeId ; let parentId = data . parentId ; let folderId = '' ; let name ; let icon ; let connection ; const nodeInfo = this . getNodeContainerInfo ( nodeId ) ; if ( nodeInfo . connectionId ) { const connectionId = NodeManagerUtils . connectionNodeIdToConnectionId ( nodeInfo . connectionId ) ; connection = this . connectionInfo . get ( connectionId ) ; } if ( NodeManagerUtils . isDatabaseObject ( nodeId ) && connection ?. connected ) { const node = await this . loadNode ( { nodeId , parentId } ) ; name = node . name ; icon = node . icon ; if ( node . folder ) { const parent = this . getNode ( node . parentId ) ; folderId = nodeId ; if ( parent && ! parent . folder ) { nodeId = parent . id ; parentId = parent . parentId ; name = parent . name ; icon = parent . icon ; } } if ( data . folderId ) { folderId = data . folderId ; } } const getParents = ( ) => { const parents = [ ] ; let parent = this . getNode ( nodeId ) ; if ( ! parent ) { return NodeManagerUtils . parentsFromPath ( nodeId ) ; } while ( parent && parent . id !== parent . parentId ) { parents . unshift ( parent . parentId ) ; parent = this . getNode ( parent . parentId ) ; } return parents ; } ; const loadParents = async ( parents ) => { let parentId = ROOT_NODE_PATH ; const nodes = await this . navNodeInfoResource . load ( resourceKeyList ( parents ) ) ; for ( const node of nodes ) { node . parentId = parentId ; parentId = node . id ; } } ; return { type : data . type , connection , nodeId , parentId , folderId , name , icon , getParents , loadParents , } ; } ; private async navigateHandler ( data , contexts ) { const nodeInfo = await contexts . getContext ( this . navigationNavNodeContext ) ; if ( NodeManagerUtils . isDatabaseObject ( nodeInfo . nodeId ) && nodeInfo . connection ) { let connection ; try { connection = await this . connectionsManagerService . requireConnection ( nodeInfo . connection . id ) ; } catch ( exception ) { this . notificationService . logException ( exception ) ; throw exception ; } if ( ! connection ?. connected ) { throw new Error ( 'Connection not established' ) ; } } } private async isNavTreeEnabled ( ) { const active = await this . permissionsService . hasAsync ( EPermission . public ) ; if ( ! active ) { return false ; } const config = await this . serverService . config . load ( ) ; if ( config ?. configurationMode ) { return false ; } return true ; } } export function parseNodeParentId ( nodeId ) { const parentId = nodeId . slice ( 0 , nodeId . lastIndexOf ( '/' ) ) ; if ( parentId === 'database:/' ) { return ROOT_NODE_PATH ; } return parentId ; }