mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
* CB-4619 feat: update gql schema * CB-4808 bulk update user parameters * CB-4808 param type fix * CB-4619 feat: updateUserPreferences API * CB-4619 refactor: settings api * CB-4619 refactor: move file * CB-4619 refactor: settings api * CB-4619 fix: pr review * CB-4619 refactor: remove web-based product settings * CB-4619 fix: after pr review * CB-4619 refactor: move product conf to cloudbeaver conf * CB-4619 fix: hide settings bar and log exception with changing session language * CB-4619 fix: revert after merge * CB-4808 new settings api * CB-4808 update product.conf format * CB-4808 update product.conf format * CB-4619 feat: support server provided settings * CB-4619 feat: prettify settings * CB-4619 chore: map sql editor settings group * CB-4808 add use short/long object name settings to CloudBeaver * CB-4808 update default product.conf * CB-4808 mode short/long settings to SQLModelPreferences * CB-4808 review fix * CB-4619 refactor: unit tests * CB-4619 fix: prevent websocket opening spam * CB-4619 fix: dependencies * CB-4619 fix: remove extra theme loading * CB-4619 fix: rename settings alias resolver file * CB-4619 fix: after review * CB-4619 fix conflicts * CB-4619 rename server property * CB-4619 remove shortname property * CB-4619 fix legacy product conf reading * CB-4619 merge root and runtime configurations * CB-4619 chore: remove log viewer setting * CB-4619 integer fields with env variables fix * CB-4619 fix: property naming * CB-4619 fix: parseJSONFlat null values * CB-4619 fix: settings loading * CB-4619 fix: save only actual settings --------- Co-authored-by: Aleksandr Skoblikov <aleksandr.skoblikov@dbeaver.com> Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
302 lines
12 KiB
TypeScript
302 lines
12 KiB
TypeScript
/*
|
|
* CloudBeaver - Cloud Database Manager
|
|
* Copyright (C) 2020-2024 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 { action, makeObservable, observable } from 'mobx';
|
|
|
|
import { ConfirmationDialog } from '@cloudbeaver/core-blocks';
|
|
import { ConnectionInfoResource, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections';
|
|
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
|
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
|
import { NotificationService } from '@cloudbeaver/core-events';
|
|
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
|
import { ICachedTreeMoveData, resourceKeyList, ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-resource';
|
|
import { ResourceManagerResource } from '@cloudbeaver/core-resource-manager';
|
|
import { NetworkStateService } from '@cloudbeaver/core-root';
|
|
import { StorageService } from '@cloudbeaver/core-storage';
|
|
import { createPath, getPathParent } from '@cloudbeaver/core-utils';
|
|
import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs';
|
|
import { NavResourceNodeService } from '@cloudbeaver/plugin-navigation-tree-rm';
|
|
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
|
import { ResourceManagerScriptsService, SCRIPTS_TYPE_ID } from '@cloudbeaver/plugin-resource-manager-scripts';
|
|
import { createSqlDataSourceHistoryInitialState, getSqlEditorName, SqlDataSourceService, SqlEditorService } from '@cloudbeaver/plugin-sql-editor';
|
|
import { SqlEditorTabService } from '@cloudbeaver/plugin-sql-editor-navigation-tab';
|
|
|
|
import type { IResourceSqlDataSourceState } from './IResourceSqlDataSourceState';
|
|
import { ResourceSqlDataSource } from './ResourceSqlDataSource';
|
|
import { SqlEditorTabResourceService } from './SqlEditorTabResourceService';
|
|
|
|
const RESOURCE_TAB_STATE = 'sql_editor_resource_tab_state';
|
|
|
|
@injectable()
|
|
export class ResourceSqlDataSourceBootstrap extends Bootstrap {
|
|
private readonly dataSourceStateState = new Map<string, IResourceSqlDataSourceState>();
|
|
|
|
constructor(
|
|
private readonly connectionInfoResource: ConnectionInfoResource,
|
|
private readonly networkStateService: NetworkStateService,
|
|
private readonly sqlDataSourceService: SqlDataSourceService,
|
|
private readonly commonDialogService: CommonDialogService,
|
|
private readonly navResourceNodeService: NavResourceNodeService,
|
|
private readonly notificationService: NotificationService,
|
|
private readonly resourceManagerService: ResourceManagerService,
|
|
private readonly sqlEditorTabService: SqlEditorTabService,
|
|
private readonly navigationTabsService: NavigationTabsService,
|
|
private readonly resourceManagerResource: ResourceManagerResource,
|
|
private readonly resourceManagerScriptsService: ResourceManagerScriptsService,
|
|
private readonly projectInfoResource: ProjectInfoResource,
|
|
private readonly sqlEditorTabResourceService: SqlEditorTabResourceService,
|
|
private readonly sqlEditorService: SqlEditorService,
|
|
private readonly projectsService: ProjectsService,
|
|
storageService: StorageService,
|
|
) {
|
|
super();
|
|
this.dataSourceStateState = new Map();
|
|
|
|
makeObservable<this, 'dataSourceStateState' | 'createState'>(this, {
|
|
createState: action,
|
|
dataSourceStateState: observable,
|
|
});
|
|
|
|
storageService.registerSettings(
|
|
RESOURCE_TAB_STATE,
|
|
this.dataSourceStateState,
|
|
() => new Map(),
|
|
map => {
|
|
for (const [key, value] of Array.from(map.entries())) {
|
|
if (
|
|
!['string', 'undefined'].includes(typeof value.resourceKey) ||
|
|
!['string'].includes(typeof value.script) ||
|
|
!['string', 'undefined'].includes(typeof value.baseScript) ||
|
|
isExecutionContextInfoValid(value.executionContext) ||
|
|
isExecutionContextInfoValid(value.baseExecutionContext)
|
|
) {
|
|
map.delete(key);
|
|
}
|
|
}
|
|
return map;
|
|
},
|
|
this.bindState.bind(this),
|
|
'indexed',
|
|
);
|
|
}
|
|
|
|
register(): void | Promise<void> {
|
|
this.resourceManagerResource.onItemDelete.addHandler(this.resourceDeleteHandler.bind(this));
|
|
this.resourceManagerResource.onMove.addHandler(this.resourceMoveHandler.bind(this));
|
|
|
|
this.sqlDataSourceService.register({
|
|
key: ResourceSqlDataSource.key,
|
|
getDataSource: (editorId, options) => {
|
|
const dataSource = new ResourceSqlDataSource(
|
|
this.networkStateService,
|
|
this.projectInfoResource,
|
|
this.connectionInfoResource,
|
|
this.resourceManagerResource,
|
|
this.sqlEditorService,
|
|
this.projectsService,
|
|
this.createState(editorId, options?.script),
|
|
);
|
|
|
|
if (options?.executionContext) {
|
|
dataSource.setExecutionContext(options.executionContext);
|
|
}
|
|
|
|
// if (options?.name) {
|
|
// dataSource.setName(options.name);
|
|
// }
|
|
|
|
dataSource.setActions({
|
|
rename: this.rename.bind(this),
|
|
read: this.read.bind(this),
|
|
write: this.write.bind(this),
|
|
getProperties: this.getProperties.bind(this),
|
|
setProperties: this.setProperties.bind(this),
|
|
});
|
|
|
|
return dataSource;
|
|
},
|
|
onDestroy: (_, editorId) => this.deleteState(editorId),
|
|
onUnload: async dataSource => {
|
|
await dataSource.save();
|
|
},
|
|
canDestroy: async (dataSource, editorId) => {
|
|
try {
|
|
await dataSource.save();
|
|
} catch {
|
|
const tab = this.sqlEditorTabService.sqlEditorTabs.find(tab => tab.handlerState.editorId === editorId);
|
|
let name: string | undefined = undefined;
|
|
|
|
if (tab) {
|
|
name = getSqlEditorName(tab.handlerState, dataSource);
|
|
}
|
|
|
|
const result = await this.commonDialogService.open(ConfirmationDialog, {
|
|
title: 'plugin_sql_editor_navigation_tab_resource_save_script_error_confirmation_title',
|
|
message: 'plugin_sql_editor_navigation_tab_resource_save_script_error_confirmation_message',
|
|
subTitle: name,
|
|
confirmActionText: 'ui_close',
|
|
});
|
|
|
|
if (result === DialogueStateResult.Rejected) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
});
|
|
}
|
|
|
|
load(): void | Promise<void> {}
|
|
|
|
private createState(editorId: string, script?: string, resourceKey?: string): IResourceSqlDataSourceState {
|
|
let state = this.dataSourceStateState.get(editorId);
|
|
|
|
if (!state) {
|
|
state = observable<IResourceSqlDataSourceState>({
|
|
script: script ?? '',
|
|
baseScript: script ?? '',
|
|
resourceKey,
|
|
history: createSqlDataSourceHistoryInitialState(script),
|
|
});
|
|
|
|
this.dataSourceStateState.set(editorId, state);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
private deleteState(editorId: string): void {
|
|
this.dataSourceStateState.delete(editorId);
|
|
}
|
|
|
|
private resourceMoveHandler(data: ICachedTreeMoveData) {
|
|
if (!this.resourceManagerService.enabled) {
|
|
return;
|
|
}
|
|
|
|
const dataSources = this.sqlDataSourceService.dataSources
|
|
.filter(([, dataSource]) => dataSource instanceof ResourceSqlDataSource)
|
|
.map(([, dataSource]) => dataSource as ResourceSqlDataSource);
|
|
|
|
for (const dataSource of dataSources) {
|
|
if (dataSource.resourceKey && dataSource.resourceKey.startsWith(data.from)) {
|
|
dataSource?.setResourceKey(data.to + dataSource.resourceKey.slice(data.from.length));
|
|
}
|
|
}
|
|
}
|
|
|
|
private resourceDeleteHandler(keyObj: ResourceKeySimple<string>) {
|
|
if (!this.resourceManagerService.enabled) {
|
|
return;
|
|
}
|
|
|
|
const tabs: string[] = [];
|
|
|
|
const dataSources = this.sqlDataSourceService.dataSources
|
|
.filter(([, dataSource]) => dataSource instanceof ResourceSqlDataSource)
|
|
.map(([, dataSource]) => dataSource as ResourceSqlDataSource);
|
|
|
|
ResourceKeyUtils.forEach(keyObj, key => {
|
|
for (const dataSource of dataSources) {
|
|
if (dataSource.resourceKey && dataSource.resourceKey.startsWith(key)) {
|
|
const tab = this.sqlEditorTabResourceService.getResourceTab(dataSource.resourceKey);
|
|
|
|
if (tab) {
|
|
tabs.push(tab.id);
|
|
}
|
|
dataSource.setResourceKey(undefined); // prevent deleted resource refresh
|
|
}
|
|
}
|
|
});
|
|
|
|
this.navigationTabsService.closeTabSilent(resourceKeyList(tabs), true);
|
|
}
|
|
|
|
private async rename(dataSource: ResourceSqlDataSource, resourceKey: string, name: string): Promise<string> {
|
|
if (!dataSource.projectId) {
|
|
throw new Error('Project ID is not defined');
|
|
}
|
|
if (!this.resourceManagerService.enabled) {
|
|
throw new Error('Resource Manager disabled');
|
|
}
|
|
|
|
try {
|
|
name = this.projectInfoResource.getNameWithExtension(dataSource.projectId, SCRIPTS_TYPE_ID, name);
|
|
const newResourceKey = createPath(getPathParent(resourceKey), name);
|
|
|
|
await this.navResourceNodeService.move(resourceKey, newResourceKey);
|
|
return newResourceKey;
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_update_script_error');
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private async write(dataSource: ResourceSqlDataSource, resourceKey: string, value: string): Promise<void> {
|
|
if (!this.resourceManagerService.enabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await this.navResourceNodeService.write(resourceKey, value);
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_update_script_error');
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private async getProperties(dataSource: ResourceSqlDataSource, resourceKey: string): Promise<IConnectionExecutionContextInfo | undefined> {
|
|
try {
|
|
return await this.resourceManagerScriptsService.getExecutionContextInfo(resourceKey);
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_sync_script_error');
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private async setProperties(
|
|
dataSource: ResourceSqlDataSource,
|
|
resourceKey: string,
|
|
executionContext: IConnectionExecutionContextInfo | undefined,
|
|
): Promise<IConnectionExecutionContextInfo | undefined> {
|
|
try {
|
|
return await this.resourceManagerScriptsService.setExecutionContextInfo(resourceKey, executionContext);
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_update_script_error');
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private async read(dataSource: ResourceSqlDataSource, resourceKey: string): Promise<string> {
|
|
try {
|
|
const data = await this.navResourceNodeService.read(resourceKey);
|
|
return data;
|
|
} catch (exception) {
|
|
this.notificationService.logException(exception as any, 'plugin_sql_editor_navigation_tab_resource_sync_script_error');
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private bindState() {
|
|
for (const [editorId, datasource] of this.sqlDataSourceService.dataSources) {
|
|
if (datasource instanceof ResourceSqlDataSource) {
|
|
datasource.bindState(this.createState(editorId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function isExecutionContextInfoValid(executionContext: IConnectionExecutionContextInfo | undefined) {
|
|
return (
|
|
!['undefined', 'object'].includes(typeof executionContext) ||
|
|
!['string', 'undefined'].includes(typeof executionContext?.connectionId) ||
|
|
!['string', 'undefined'].includes(typeof executionContext?.id) ||
|
|
!['string', 'undefined', 'object'].includes(typeof executionContext?.defaultCatalog) ||
|
|
!['string', 'undefined', 'object'].includes(typeof executionContext?.defaultSchema)
|
|
);
|
|
}
|