diff --git a/server/bundles/io.cloudbeaver.server/schema/schema.graphqls b/server/bundles/io.cloudbeaver.server/schema/schema.graphqls index e75d8f8826..7821bb81f2 100644 --- a/server/bundles/io.cloudbeaver.server/schema/schema.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/schema.graphqls @@ -55,14 +55,14 @@ type WebServiceConfig { } type ServerConfig { - name: String - version: String + name: String! + version: String! supportsPredefinedConnections: Boolean supportsProvidedConnections: Boolean supportsCustomConnections: Boolean supportsConnectionBrowser: Boolean supportsWorkspaces: Boolean - supportedLanguages: [ ServerLanguage ] + supportedLanguages: [ ServerLanguage ]! services: [ WebServiceConfig ] productConfiguration: Object! } @@ -70,12 +70,12 @@ type ServerConfig { type SessionInfo { id: ID! - createTime: String - lastAccessTime: String - locale: String + createTime: String! + lastAccessTime: String! + locale: String! serverMessages: [ ServerMessage ] - connections: [ ConnectionInfo ] + connections: [ ConnectionInfo ]! } #################################################### diff --git a/webapp/packages/core/configs/rollup.config.js b/webapp/packages/core/configs/rollup.config.js index 2be8deef42..673b6e1ffb 100644 --- a/webapp/packages/core/configs/rollup.config.js +++ b/webapp/packages/core/configs/rollup.config.js @@ -12,11 +12,14 @@ const modules = [ // The order of modules is important. // The module must be built before it is used in another module + '/extensions', '/di', '/utils', '/eventsLog', '/settings', + '/product', + '/plugin', '/theming', '/localization', diff --git a/webapp/packages/core/configs/webpack/exp.js b/webapp/packages/core/configs/webpack/exp.js index aec151dce2..02672abfd2 100644 --- a/webapp/packages/core/configs/webpack/exp.js +++ b/webapp/packages/core/configs/webpack/exp.js @@ -14,6 +14,9 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { dialogs: "./dialogs/index.ts", eventsLog: "./eventsLog/index.ts", localization: "./localization/index.ts", + extensions: "./extensions/index.ts", + plugin: "./plugin/index.ts", + product: "./product/index.ts", // queries dir - ignore root: "./root/index.ts", sdk: "./sdk/index.ts", diff --git a/webapp/packages/core/plugin/package.json b/webapp/packages/core/plugin/package.json new file mode 100644 index 0000000000..238a43f949 --- /dev/null +++ b/webapp/packages/core/plugin/package.json @@ -0,0 +1,8 @@ +{ + "name": "@dbeaver/core/plugin", + "private": true, + "sideEffects": false, + "main": "../dist/plugin", + "module": "../dist/plugin", + "types": "../dist/types/plugin" +} diff --git a/webapp/packages/core/product/package.json b/webapp/packages/core/product/package.json new file mode 100644 index 0000000000..333f59dd0a --- /dev/null +++ b/webapp/packages/core/product/package.json @@ -0,0 +1,8 @@ +{ + "name": "@dbeaver/core/product", + "private": true, + "sideEffects": false, + "main": "../dist/product", + "module": "../dist/product", + "types": "../dist/types/product" +} diff --git a/webapp/packages/core/src/AppBootstrap.ts b/webapp/packages/core/src/AppBootstrap.ts index aa41c02045..a138b389e6 100644 --- a/webapp/packages/core/src/AppBootstrap.ts +++ b/webapp/packages/core/src/AppBootstrap.ts @@ -14,9 +14,11 @@ import { } from '@dbeaver/core/app'; import { injectable } from '@dbeaver/core/di'; import { ExceptionsCatcherService } from '@dbeaver/core/eventsLog'; -import { SessionService } from '@dbeaver/core/root'; import { ThemeService } from '@dbeaver/core/theming'; +import { SessionExpireService } from './dialogs'; +import { LocalizationService } from './localization'; + /** * AppBootstrap.init() will be executed between first and second phase of App initialization, * when application has registered all dependencies but plugins did'nt initialized. @@ -27,11 +29,12 @@ import { ThemeService } from '@dbeaver/core/theming'; export class AppBootstrap { constructor(private exceptionsCatcherService: ExceptionsCatcherService, - private sessionService: SessionService, + private localizationService: LocalizationService, private themeService: ThemeService, private connectionDialogService: ConnectionDialogsService, private connectionsManager: ConnectionsManagerService, private logViewerMenuService: LogViewerMenuService, + private sessionExpireService: SessionExpireService, private navigationTreeContextMenuService: NavigationTreeContextMenuService, private nodesManagerService: NodesManagerService, private navigationTabsService: NavigationTabsService, @@ -40,8 +43,9 @@ export class AppBootstrap { async init() { this.exceptionsCatcherService.subscribe(); + this.sessionExpireService.subscribe(); - await this.sessionService.init(); + await this.localizationService.init(); await this.themeService.init(); this.connectionSchemaManagerService.registerCallbacks(); diff --git a/webapp/packages/core/src/CoreSettingsService.ts b/webapp/packages/core/src/CoreSettingsService.ts new file mode 100644 index 0000000000..71b7aaa4d4 --- /dev/null +++ b/webapp/packages/core/src/CoreSettingsService.ts @@ -0,0 +1,25 @@ +/* + * 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 '@dbeaver/core/di'; +import { PluginManagerService } from '@dbeaver/core/plugin'; + +const defaultSettings = { + 'app.logViewer.refreshInterval': 3000, + 'app.logViewer.maxLogEntries': 1000, +}; + +export type CoreSettings = typeof defaultSettings + +@injectable() +export class CoreSettingsService { + + readonly settings = this.pluginManagerService.getPluginSettings('core', defaultSettings); + + constructor(private pluginManagerService: PluginManagerService) { } +} diff --git a/webapp/packages/core/src/app/RightArea.tsx b/webapp/packages/core/src/app/RightArea.tsx index cc4ffc5556..145b780700 100644 --- a/webapp/packages/core/src/app/RightArea.tsx +++ b/webapp/packages/core/src/app/RightArea.tsx @@ -12,9 +12,9 @@ import styled, { css } from 'reshadow'; import { Pane, ResizerControls, Split, splitHorizontalStyles, splitStyles, } from '@dbeaver/core/blocks'; +import { useController } from '@dbeaver/core/di'; import { composes, useStyles } from '@dbeaver/core/theming'; -import { useController } from '../di'; import { NavigationTabsBar } from './shared/NavigationTabs/NavigationTabsBar'; import { LogViewTab } from './shared/ToolsPanel/LogViewTab/LogViewTab'; import { LogViewTabController } from './shared/ToolsPanel/LogViewTab/LogViewTabController'; diff --git a/webapp/packages/core/src/app/TopNavBar/TopNavBar.tsx b/webapp/packages/core/src/app/TopNavBar/TopNavBar.tsx index d555c2ed50..7a31466a7d 100644 --- a/webapp/packages/core/src/app/TopNavBar/TopNavBar.tsx +++ b/webapp/packages/core/src/app/TopNavBar/TopNavBar.tsx @@ -11,7 +11,7 @@ import styled from 'reshadow'; import { Icon } from '@dbeaver/core/blocks'; import { useService } from '@dbeaver/core/di'; -import { SessionService } from '@dbeaver/core/root'; +import { ServerService } from '@dbeaver/core/root'; import { useStyles } from '@dbeaver/core/theming'; import { ConnectionSelector } from './ConnectionSchemaManager/ConnectionSelector/ConnectionSelector'; @@ -23,8 +23,8 @@ import { topNavBarStyles } from './topNavBarStyles'; declare const version: string; // declared in webpack DefinePlugin // todo move to enviroment? export const TopNavBar = observer(function TopNavBar() { - const sessionService = useService(SessionService); - const title = `Frontend: ${version}\nBackend: ${sessionService.version}`; + const serverService = useService(ServerService); + const title = `Frontend: ${version}\nBackend: ${serverService.config.data?.version}`; return styled(useStyles(topNavBarStyles))(
diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts index c68afdcb8e..1eaa86765a 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts @@ -10,13 +10,12 @@ import { computed, observable } from 'mobx'; import { Subject } from 'rxjs'; import { injectable } from '@dbeaver/core/di'; -import { ConnectionShortInfo, SessionService } from '@dbeaver/core/root'; +import { SessionService } from '@dbeaver/core/root'; import { ConnectionInfo, DataSourceInfo, DriverInfo, GraphQLService, - NavGetStructContainersQuery, CachedResource, DatabaseObjectInfo, } from '@dbeaver/core/sdk'; @@ -118,9 +117,16 @@ export class ConnectionsManagerService { } async restoreConnections() { - for (const connection of this.sessionService.getConnections()) { - this.restoreConnection(connection); + const config = await this.sessionService.session.load(); + if (!config) { + return; } + + // TODO: connections must be string[] + for (const connection of config.connections) { + await this.restoreConnection(connection); + } + await this.nodesManagerService.updateRootChildren(); } private async refreshObjectContainersAsync( @@ -130,7 +136,10 @@ export class ConnectionsManagerService { catalogId?: string, ): Promise> { if (refresh || !data.has(connectionId)) { - const { navGetStructContainers } = await this.loadSchemasAndCatalogs(connectionId, catalogId); + const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({ + connectionId, + catalogId, + }); data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]); } @@ -149,18 +158,10 @@ export class ConnectionsManagerService { return data; } - /** - * Note that this request returns either schemaList or catalogList. You never got both lists together - */ - private async loadSchemasAndCatalogs(connectionId: string, catalogId?: string): Promise { - return this.graphQLService.gql.navGetStructContainers({ connectionId, catalogId }); - } + private async restoreConnection(connection: Connection) { + // TODO: Must be loaded based on connection id + // const { connection } = await this.graphQLService.gql.connectionState({ id }); - private restoreConnection(connectionInfo: ConnectionShortInfo) { - const connection: Connection = { - ...connectionInfo, - connected: true, - }; this.connectionsMap.set(connection.id, connection); this.onOpenConnection.next(connection); } diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionProvider.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionProvider.ts index 185a54b939..084f40124f 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionProvider.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionProvider.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const connectionProviderSymbol = Symbol('@extension/ConnectionProvider'); diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionSetter.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionSetter.ts index d21b5ab4e4..f677c4abf7 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionSetter.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/extensions/IConnectionSetter.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const connectionSetterSymbol = Symbol('@extension/ConnectionSetter'); diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/queries/connectionState.gql b/webapp/packages/core/src/app/shared/ConnectionsManager/queries/connectionState.gql new file mode 100644 index 0000000000..6c9eb8d3a3 --- /dev/null +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/queries/connectionState.gql @@ -0,0 +1,8 @@ +query connectionState($id: ID!) { + connection: connectionState(id: $id) { + id + name + driverId + connected + } +} diff --git a/webapp/packages/core/src/app/shared/NavigationTabs/TabHandler.ts b/webapp/packages/core/src/app/shared/NavigationTabs/TabHandler.ts index 247899e583..b3942eeac8 100644 --- a/webapp/packages/core/src/app/shared/NavigationTabs/TabHandler.ts +++ b/webapp/packages/core/src/app/shared/NavigationTabs/TabHandler.ts @@ -6,8 +6,9 @@ * you may not use this file except in compliance with the License. */ -import { IExtension } from '../../../extensions'; -import { Style } from '../../../theming'; +import { IExtension } from '@dbeaver/core/extensions'; +import { Style } from '@dbeaver/core/theming'; + import { ITab } from './ITab'; export type TabHandlerTabProps = { diff --git a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogProvider.ts b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogProvider.ts index c4c8155fa4..d2fd0d6da3 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogProvider.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogProvider.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const objectCatalogProviderSymbol = Symbol('@extension/ObjectCatalogProvider'); diff --git a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogSetter.ts b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogSetter.ts index 32b9a1aac2..bbe1cd5939 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogSetter.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectCatalogSetter.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const objectCatalogSetterSymbol = Symbol('@extension/ObjectCatalogSetter'); diff --git a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaProvider.ts b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaProvider.ts index 50d4358fd5..98c0bd088e 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaProvider.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaProvider.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const objectSchemaProviderSymbol = Symbol('@extension/ObjectSchemaProvider'); diff --git a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaSetter.ts b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaSetter.ts index 37b5efe2ee..cffbccf6d8 100644 --- a/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaSetter.ts +++ b/webapp/packages/core/src/app/shared/NodesManager/extensions/IObjectSchemaSetter.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { createExtension, isExtension, IExtension } from '../../../../extensions'; +import { createExtension, isExtension, IExtension } from '@dbeaver/core/extensions'; const objectSchemaSetterSymbol = Symbol('@extension/ObjectSchemaSetter'); diff --git a/webapp/packages/core/src/app/shared/ToolsPanel/LogViewTab/LogViewerService.ts b/webapp/packages/core/src/app/shared/ToolsPanel/LogViewTab/LogViewerService.ts index 0f391cbfee..5104ec13fa 100644 --- a/webapp/packages/core/src/app/shared/ToolsPanel/LogViewTab/LogViewerService.ts +++ b/webapp/packages/core/src/app/shared/ToolsPanel/LogViewTab/LogViewerService.ts @@ -11,16 +11,11 @@ import { action, observable } from 'mobx'; import { injectable } from '@dbeaver/core/di'; import { NotificationService } from '@dbeaver/core/eventsLog'; import { GraphQLService } from '@dbeaver/core/sdk'; -import { SettingsService } from '@dbeaver/core/settings'; import { uuid } from '@dbeaver/core/utils'; +import { CoreSettingsService } from '../../../../CoreSettingsService'; import { ILogEntry } from './ILogEntry'; -const REFRESH_INTERVAL = 3000; -const REFRESH_INTERVAL_TOKEN = 'core.app.logViewer.refreshInterval'; -const MAX_LOG_ENTRIES = 1000; -const MAX_LOG_ENTRIES_TOKEN = 'core.app.logViewer.maxLogEntries'; - @injectable() export class LogViewerService { @@ -28,11 +23,9 @@ export class LogViewerService { @observable private log: ILogEntry[] = []; private interval: any = null; - private refreshInterval = this.settingsService.getProperty(REFRESH_INTERVAL_TOKEN, REFRESH_INTERVAL); - private maxLogEntries = this.settingsService.getProperty(MAX_LOG_ENTRIES_TOKEN, MAX_LOG_ENTRIES); constructor(private graphQLService: GraphQLService, - private settingsService: SettingsService, + private coreSettingsService: CoreSettingsService, private notificationService: NotificationService) { } @@ -55,9 +48,10 @@ export class LogViewerService { return; } await this.updateLog(); + const refreshInterval = this.coreSettingsService.settings.getValue('app.logViewer.refreshInterval'); this.interval = setInterval(() => { this.updateLog(); - }, this.refreshInterval); + }, refreshInterval); } stopLog() { @@ -84,14 +78,16 @@ export class LogViewerService { @action addNewEntries(entries: ILogEntry[]) { this.log.unshift(...entries.reverse()); - if (this.log.length > this.maxLogEntries) { - this.log.splice(this.maxLogEntries, this.log.length - this.maxLogEntries); + const maxLogEntries = this.coreSettingsService.settings.getValue('app.logViewer.maxLogEntries'); + if (this.log.length > maxLogEntries) { + this.log.splice(maxLogEntries, this.log.length - maxLogEntries); } } async loadLog(): Promise { + const maxLogEntries = this.coreSettingsService.settings.getValue('app.logViewer.maxLogEntries'); const { log } = await this.graphQLService.gql.readSessionLog({ - maxEntries: this.maxLogEntries, + maxEntries: maxLogEntries, clearEntries: true, }); const entries: ILogEntry[] = (log || []).map(item => ({ diff --git a/webapp/packages/core/src/blocks/ErrorMessage.tsx b/webapp/packages/core/src/blocks/ErrorMessage.tsx index c68b5bf06b..79e5cac719 100644 --- a/webapp/packages/core/src/blocks/ErrorMessage.tsx +++ b/webapp/packages/core/src/blocks/ErrorMessage.tsx @@ -9,8 +9,9 @@ import { observer } from 'mobx-react'; import styled, { css } from 'reshadow'; -import { useTranslate } from '../localization'; -import { useStyles, composes } from '../theming'; +import { useTranslate } from '@dbeaver/core/localization'; +import { useStyles, composes } from '@dbeaver/core/theming'; + import { Button } from './Button'; diff --git a/webapp/packages/core/src/blocks/InputField.tsx b/webapp/packages/core/src/blocks/InputField.tsx index 72ec21c520..1328b531ee 100644 --- a/webapp/packages/core/src/blocks/InputField.tsx +++ b/webapp/packages/core/src/blocks/InputField.tsx @@ -8,7 +8,7 @@ import styled, { css, use } from 'reshadow'; -import { useStyles } from '../theming'; +import { useStyles } from '@dbeaver/core/theming'; const styles = css` label { diff --git a/webapp/packages/core/src/blocks/Loader/loaderStyles.ts b/webapp/packages/core/src/blocks/Loader/loaderStyles.ts index 968abe72d2..75b48fa67f 100644 --- a/webapp/packages/core/src/blocks/Loader/loaderStyles.ts +++ b/webapp/packages/core/src/blocks/Loader/loaderStyles.ts @@ -8,7 +8,7 @@ import { css } from 'reshadow'; -import { composes } from '../../theming/themeUtils'; +import { composes } from '@dbeaver/core/theming'; export const loaderStyles = css` loader { diff --git a/webapp/packages/core/src/dialogs/Menu/IMenuPanel.ts b/webapp/packages/core/src/dialogs/Menu/IMenuPanel.ts index 6746d06c7c..0f325fc74d 100644 --- a/webapp/packages/core/src/dialogs/Menu/IMenuPanel.ts +++ b/webapp/packages/core/src/dialogs/Menu/IMenuPanel.ts @@ -9,8 +9,7 @@ import { ButtonHTMLAttributes, PropsWithChildren } from 'react'; import { TLocalizationToken } from '@dbeaver/core/localization'; - -import { Style } from '../../theming/useStyles'; +import { Style } from '@dbeaver/core/theming'; export type MenuMod = 'primary' | 'surface' | 'secondary'; diff --git a/webapp/packages/core/src/dialogs/SessionExpire/SessionExpireService.ts b/webapp/packages/core/src/dialogs/SessionExpire/SessionExpireService.ts new file mode 100644 index 0000000000..345a04c4ce --- /dev/null +++ b/webapp/packages/core/src/dialogs/SessionExpire/SessionExpireService.ts @@ -0,0 +1,48 @@ +/* + * 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 } from 'rxjs'; + +import { injectable } from '@dbeaver/core/di'; +import { + GQLError, GraphQLService, EServerErrorCode +} from '@dbeaver/core/sdk'; + +import { CommonDialogService } from '../CommonDialog/CommonDialogService'; +import { SessionExpiredDialog } from './SessionExpiredDialog'; + +@injectable() +export class SessionExpireService { + private isNotifiedAboutExpiredSession = false; + + onSessionExpire = new Subject(); + constructor( + private graphQLService: GraphQLService, + private commonDialogService: CommonDialogService + ) { + } + + subscribe() { + this.graphQLService.registerInterceptor(this.sessionExpiredInterceptor.bind(this)); + } + + private async sessionExpiredInterceptor(request: Promise): Promise { + try { + return await request; + } catch (exception) { + if (exception instanceof GQLError + && exception.errorCode === EServerErrorCode.sessionExpired + && !this.isNotifiedAboutExpiredSession) { + this.isNotifiedAboutExpiredSession = true; + this.onSessionExpire.next(); + await this.commonDialogService.open(SessionExpiredDialog, null); + } + throw exception; + } + } +} diff --git a/webapp/packages/core/src/root/SessionExpiredDialog.tsx b/webapp/packages/core/src/dialogs/SessionExpire/SessionExpiredDialog.tsx similarity index 91% rename from webapp/packages/core/src/root/SessionExpiredDialog.tsx rename to webapp/packages/core/src/dialogs/SessionExpire/SessionExpiredDialog.tsx index da81c33bc7..60a54e4643 100644 --- a/webapp/packages/core/src/root/SessionExpiredDialog.tsx +++ b/webapp/packages/core/src/dialogs/SessionExpire/SessionExpiredDialog.tsx @@ -11,10 +11,12 @@ import { useCallback } from 'react'; import styled, { css } from 'reshadow'; import { Button } from '@dbeaver/core/blocks'; -import { CommonDialogWrapper, DialogComponent } from '@dbeaver/core/dialogs'; import { useTranslate } from '@dbeaver/core/localization'; import { useStyles } from '@dbeaver/core/theming'; +import { DialogComponent } from '../CommonDialog/CommonDialogService'; +import { CommonDialogWrapper } from '../CommonDialog/CommonDialogWrapper'; + const styles = css` dialog-content { display: flex; diff --git a/webapp/packages/core/src/dialogs/index.ts b/webapp/packages/core/src/dialogs/index.ts index a25db353a6..01d5b7d111 100644 --- a/webapp/packages/core/src/dialogs/index.ts +++ b/webapp/packages/core/src/dialogs/index.ts @@ -13,3 +13,5 @@ export * from './Menu/models/ComputedMenuPanelModel'; export * from './Menu/ContextMenu/ContextMenuService'; export * from './Menu/ContextMenu/IContextMenuItem'; export * from './Menu/ContextMenu/IMenuContext'; + +export * from './SessionExpire/SessionExpireService'; diff --git a/webapp/packages/core/src/localization/LocalizationService.ts b/webapp/packages/core/src/localization/LocalizationService.ts index e55fa1b7ae..deeac786f9 100644 --- a/webapp/packages/core/src/localization/LocalizationService.ts +++ b/webapp/packages/core/src/localization/LocalizationService.ts @@ -14,6 +14,7 @@ import { NotificationService } from '@dbeaver/core/eventsLog'; import { GraphQLService, ServerLanguage } from '@dbeaver/core/sdk'; import { SettingsService } from '@dbeaver/core/settings'; +import { SessionService, ServerService } from '../root'; import { Locale } from './Locale'; import { TLocalizationToken } from './TLocalizationToken'; @@ -37,6 +38,8 @@ export class LocalizationService { private localeMap: Map = new Map(); constructor(private notificationService: NotificationService, + private sessionService: SessionService, + private serverService: ServerService, private graphQLService: GraphQLService, private settingsService: SettingsService) { @@ -78,13 +81,19 @@ export class LocalizationService { return locale && locale[token]; } - async init(sessionLanguage: string | undefined, supportedLanguages: ServerLanguageShort[]) { - this.supportedLanguages = supportedLanguages; + async init() { + const session = await this.sessionService.session.load(); + const config = await this.serverService.config.load(); + + if (!session || !config) { + return; + } + + this.supportedLanguages = config.supportedLanguages; this.settingsService.registerSettings(this.settings, LANG_SETTINGS_KEY); // overwrite default value with settings - if (sessionLanguage) { - this.setCurrentLocale(sessionLanguage); // session language wins - } + this.setCurrentLocale(session.locale); // session language wins + await this.setLocale(this.getCurrentLanguage()); } diff --git a/webapp/packages/core/src/manifest.ts b/webapp/packages/core/src/manifest.ts index 8a42d71911..9c00618b09 100644 --- a/webapp/packages/core/src/manifest.ts +++ b/webapp/packages/core/src/manifest.ts @@ -14,14 +14,20 @@ import { SettingsMenuService, LogViewerService, LogViewerMenuService, } from '@dbeaver/core/app'; import { PluginManifest } from '@dbeaver/core/di'; -import { CommonDialogService, ContextMenuService } from '@dbeaver/core/dialogs'; +import { CommonDialogService, ContextMenuService, SessionExpireService } from '@dbeaver/core/dialogs'; import { NotificationService, ExceptionsCatcherService } from '@dbeaver/core/eventsLog'; import { LocalizationService } from '@dbeaver/core/localization'; -import { SessionService } from '@dbeaver/core/root'; +import { PluginManagerService } from '@dbeaver/core/plugin'; +import { ProductManagerService, ProductSettingsService } from '@dbeaver/core/product'; +import { + SessionService, ServerService, SessionSettingsService, ServerSettingsService +} from '@dbeaver/core/root'; import { EnvironmentService, GraphQLService } from '@dbeaver/core/sdk'; import { LocalStorageSaveService, SettingsService } from '@dbeaver/core/settings'; import { ThemeService } from '@dbeaver/core/theming'; +import { CoreSettingsService } from './CoreSettingsService'; + export const coreManifest: PluginManifest = { info: { name: 'DBeaver core', @@ -29,7 +35,14 @@ export const coreManifest: PluginManifest = { depends: [], providers: [ + ProductSettingsService, + ProductManagerService, + PluginManagerService, + ServerSettingsService, + SessionSettingsService, + CoreSettingsService, CommonDialogService, + SessionExpireService, ConnectionDialogsService, ConnectionSchemaManagerService, ConnectionsManagerService, @@ -52,6 +65,7 @@ export const coreManifest: PluginManifest = { SettingsMenuService, SettingsService, ThemeService, + ServerService, ], async initialize(services) { diff --git a/webapp/packages/core/src/plugin/PluginManagerService.ts b/webapp/packages/core/src/plugin/PluginManagerService.ts new file mode 100644 index 0000000000..22c082bddd --- /dev/null +++ b/webapp/packages/core/src/plugin/PluginManagerService.ts @@ -0,0 +1,23 @@ +/* + * 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 '@dbeaver/core/di'; +import { ProductManagerService } from '@dbeaver/core/product'; + +import { PluginSettings } from './PluginSettings'; + + +@injectable() +export class PluginManagerService { + + constructor(private productManagerService: ProductManagerService) { } + + getPluginSettings(scope: string, defaults: T) { + return new PluginSettings(this.productManagerService.settings, scope, defaults); + } +} diff --git a/webapp/packages/core/src/plugin/PluginSettings.ts b/webapp/packages/core/src/plugin/PluginSettings.ts new file mode 100644 index 0000000000..0482c9b8e7 --- /dev/null +++ b/webapp/packages/core/src/plugin/PluginSettings.ts @@ -0,0 +1,38 @@ +/* + * 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 { ISettingsSource } from '@dbeaver/core/settings'; + +export class PluginSettings implements ISettingsSource { + constructor( + private source: ISettingsSource, + private scope: string, + private defaults: T + ) { + } + + has(key: TKey): boolean { + return true; + } + + getValue(key: TKey): T[TKey] { + if (this.source.has(this.scopedKey(key))) { + return this.source.getValue(this.scopedKey(key)); + } + + return this.defaults[key]; + } + + setValue(key: TKey, value: T[TKey]) { + this.source.setValue(this.scopedKey(key), value); + } + + private scopedKey(key: string | number | symbol): string { + return `${this.scope}.${String(key)}`; + } +} diff --git a/webapp/packages/core/src/plugin/index.ts b/webapp/packages/core/src/plugin/index.ts new file mode 100644 index 0000000000..dc1ffe0189 --- /dev/null +++ b/webapp/packages/core/src/plugin/index.ts @@ -0,0 +1,2 @@ +export * from './PluginSettings'; +export * from './PluginManagerService'; diff --git a/webapp/packages/core/src/product/ProductManagerService.ts b/webapp/packages/core/src/product/ProductManagerService.ts new file mode 100644 index 0000000000..c640edf619 --- /dev/null +++ b/webapp/packages/core/src/product/ProductManagerService.ts @@ -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 '@dbeaver/core/di'; +import { ServerService } from '@dbeaver/core/root'; +import { parseJSONFlat } from '@dbeaver/core/utils'; + +import { ProductSettingsService } from './ProductSettingsService'; + +@injectable() +export class ProductManagerService { + + readonly settings = new ProductSettingsService(this.serverService.settings) + + constructor(private serverService: ServerService) { } + + setSettings(object: any) { + + parseJSONFlat( + object, + this.settings.setSelfValue.bind(this.settings), + undefined + ); + } +} diff --git a/webapp/packages/core/src/product/ProductSettingsService.ts b/webapp/packages/core/src/product/ProductSettingsService.ts new file mode 100644 index 0000000000..0a7932550d --- /dev/null +++ b/webapp/packages/core/src/product/ProductSettingsService.ts @@ -0,0 +1,26 @@ +/* + * 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 '@dbeaver/core/di'; +import { SettingsSource, ISettingsSource } from '@dbeaver/core/settings'; + +@injectable() +export class ProductSettingsService extends SettingsSource { + + constructor(fallback: ISettingsSource) { + super(fallback); + } + + setValue(key: string, value: any) { + this.fallback?.setValue(key, value); + } + + setSelfValue(key: string, value: string) { + super.setValue(key, value); + } +} diff --git a/webapp/packages/core/src/product/index.ts b/webapp/packages/core/src/product/index.ts new file mode 100644 index 0000000000..76cae25e3f --- /dev/null +++ b/webapp/packages/core/src/product/index.ts @@ -0,0 +1,2 @@ +export * from './ProductManagerService'; +export * from './ProductSettingsService'; diff --git a/webapp/packages/core/src/root/ServerService.ts b/webapp/packages/core/src/root/ServerService.ts new file mode 100644 index 0000000000..3ac6c500bb --- /dev/null +++ b/webapp/packages/core/src/root/ServerService.ts @@ -0,0 +1,46 @@ +/* + * 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 '@dbeaver/core/di'; +import { GraphQLService, ServerConfig, CachedResource } from '@dbeaver/core/sdk'; +import { parseJSONFlat } from '@dbeaver/core/utils'; + +import { ServerSettingsService } from './ServerSettingsService'; +import { SessionService } from './SessionService'; + +@injectable() +export class ServerService { + readonly config = new CachedResource(undefined, this.refreshConfigAsync.bind(this)); + readonly settings = new ServerSettingsService(this.sessionService.settings); + + private lastConfig: any = null + constructor( + private graphQLService: GraphQLService, + private sessionService: SessionService, + ) { + } + + private async refreshConfigAsync(data: ServerConfig | undefined): Promise { + if (data) { + return data; + } + + const { serverConfig } = await this.graphQLService.gql.serverConfig(); + + if (serverConfig.productConfiguration !== this.lastConfig) { + this.lastConfig = serverConfig.productConfiguration; + this.settings.clear(); + parseJSONFlat( + serverConfig.productConfiguration, + this.settings.setSelfValue.bind(this.settings), + undefined + ); + } + return serverConfig; + } +} diff --git a/webapp/packages/core/src/root/ServerSettingsService.ts b/webapp/packages/core/src/root/ServerSettingsService.ts new file mode 100644 index 0000000000..1aa2031857 --- /dev/null +++ b/webapp/packages/core/src/root/ServerSettingsService.ts @@ -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 '@dbeaver/core/di'; +import { SettingsSource, ISettingsSource } from '@dbeaver/core/settings'; + +@injectable() +export class ServerSettingsService extends SettingsSource { + + constructor(fallback: ISettingsSource) { + super(fallback); + } + + setValue(key: string, value: string) { + this.fallback?.setValue(key, value); + } + + setSelfValue(key: string, value: string) { + super.setValue(key, value); + } + + clear() { + this.store.clear(); + } +} diff --git a/webapp/packages/core/src/root/SessionService.ts b/webapp/packages/core/src/root/SessionService.ts index 4c66a3e6aa..e475761085 100644 --- a/webapp/packages/core/src/root/SessionService.ts +++ b/webapp/packages/core/src/root/SessionService.ts @@ -6,98 +6,31 @@ * you may not use this file except in compliance with the License. */ -import { computed } from 'mobx'; - import { injectable } from '@dbeaver/core/di'; -import { CommonDialogService } from '@dbeaver/core/dialogs'; -import { LocalizationService } from '@dbeaver/core/localization'; import { - ConnectionInfo, GQLError, GraphQLService, ServerLanguage, SessionInfo, + ConnectionInfo, GraphQLService, SessionInfo, CachedResource, } from '@dbeaver/core/sdk'; -import { EServerErrorCode } from '../sdk/EServerErrorCode'; -import { SessionExpiredDialog } from './SessionExpiredDialog'; +import { SessionSettingsService } from './SessionSettingsService'; -export type ConnectionShortInfo = Pick; -export type SessionShortInfo = Pick; - -export type Session = { - name: string; - version: string; - language: string; - supportedLanguages: Pick[]; - connections: ConnectionShortInfo[]; -} - -export type Server = { - name: string; - version: string; - supportedLanguages: ConnectionShortInfo[]; -} +export type SessionState = Pick & { + connections: Array>; +}; @injectable() export class SessionService { - private session: Session = { - name: '', - version: '', - language: 'en', - supportedLanguages: [], - connections: [], - }; + readonly session = new CachedResource(undefined, this.refreshSessionStateAsync.bind(this)); + readonly settings = new SessionSettingsService('session_settings'); - @computed get version() { - return this.session.version; - } + constructor(private graphQLService: GraphQLService) {} - private isNotifiedAboutExpiredSession = false; - - constructor(private graphQLService: GraphQLService, - private localizationService: LocalizationService, - private commonDialogService: CommonDialogService) { - this.graphQLService.registerInterceptor(this.sessionExpiredInterceptor.bind(this)); - } - - getConnections(): ConnectionShortInfo[] { - return this.session.connections; - } - - async init(): Promise { - this.session = await this.fetchCurrentSession(); - - this.localizationService.init(this.session.language, this.session.supportedLanguages); - - } - - private async sessionExpiredInterceptor(request: Promise): Promise { - try { - return await request; - } catch (exception) { - if (exception instanceof GQLError - && exception.errorCode === EServerErrorCode.sessionExpired - && !this.isNotifiedAboutExpiredSession) { - this.isNotifiedAboutExpiredSession = true; - await this.commonDialogService.open(SessionExpiredDialog, null); - } - throw exception; + private async refreshSessionStateAsync(data: SessionState | undefined): Promise { + if (data) { + return data; } - } - private async fetchCurrentSession(): Promise { - const { serverConfig } = await this.graphQLService.gql.serverConfig(); - const sessionInfo = await this.openNewSession(); + const { session } = await this.graphQLService.gql.openSession(); - const session: Session = { - name: serverConfig.name || 'WHERE IS A NAME', - version: serverConfig.version || 'WHERE IS A VERSION', - language: sessionInfo.locale || 'en', - supportedLanguages: serverConfig.supportedLanguages, - connections: sessionInfo.connections || [], - }; return session; } - - private async openNewSession(): Promise { - const response = await this.graphQLService.gql.openSession(); - return response.session; - } } diff --git a/webapp/packages/core/src/root/SessionSettingsService.ts b/webapp/packages/core/src/root/SessionSettingsService.ts new file mode 100644 index 0000000000..1a5b615cdd --- /dev/null +++ b/webapp/packages/core/src/root/SessionSettingsService.ts @@ -0,0 +1,27 @@ +/* + * 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 '@dbeaver/core/di'; +import { SettingsSource } from '@dbeaver/core/settings'; + +@injectable() +export class SessionSettingsService extends SettingsSource { + + constructor(private localStorageKey: string) { + super(); + const state = localStorage.getItem(this.localStorageKey); + if (state) { + this.store = JSON.parse(state); + } + } + + setValue(key: string, value: string) { + super.setValue(key, value); + localStorage.setItem(this.localStorageKey, JSON.stringify(this.store)); + } +} diff --git a/webapp/packages/core/src/root/index.ts b/webapp/packages/core/src/root/index.ts index 468ee859c8..08ca84e35b 100644 --- a/webapp/packages/core/src/root/index.ts +++ b/webapp/packages/core/src/root/index.ts @@ -1 +1,4 @@ export * from './SessionService'; +export * from './ServerService'; +export * from './ServerSettingsService'; +export * from './SessionSettingsService'; diff --git a/webapp/packages/core/src/root/queries/openSession.gql b/webapp/packages/core/src/root/queries/openSession.gql index 1d1b852393..889a61b8bc 100644 --- a/webapp/packages/core/src/root/queries/openSession.gql +++ b/webapp/packages/core/src/root/queries/openSession.gql @@ -1,10 +1,14 @@ mutation openSession { session: openSession { + id + createTime + lastAccessTime + locale connections { id - name, + name driverId + connected } - locale } } diff --git a/webapp/packages/core/src/root/queries/serverConfig.gql b/webapp/packages/core/src/root/queries/serverConfig.gql index 3385b3058e..44ef426a82 100644 --- a/webapp/packages/core/src/root/queries/serverConfig.gql +++ b/webapp/packages/core/src/root/queries/serverConfig.gql @@ -2,9 +2,17 @@ query serverConfig { serverConfig { name version + productConfiguration + supportsPredefinedConnections + supportsProvidedConnections + supportsCustomConnections + supportsConnectionBrowser + supportsWorkspaces supportedLanguages { isoCode + displayName nativeName } + productConfiguration } } diff --git a/webapp/packages/core/src/root/queries/sessionState.gql b/webapp/packages/core/src/root/queries/sessionState.gql index 12c6fbcd9e..c92ddca804 100644 --- a/webapp/packages/core/src/root/queries/sessionState.gql +++ b/webapp/packages/core/src/root/queries/sessionState.gql @@ -1,10 +1,14 @@ query sessionState { sessionState { + id + createTime + lastAccessTime + locale connections { id - name, + name driverId + connected } - locale } } diff --git a/webapp/packages/core/src/sdk/index.ts b/webapp/packages/core/src/sdk/index.ts index ee53fbc5b6..11de1dd097 100644 --- a/webapp/packages/core/src/sdk/index.ts +++ b/webapp/packages/core/src/sdk/index.ts @@ -4,3 +4,4 @@ export * from './sdk'; export * from './GQLError'; export * from './ServerInternalError'; export * from './CachedResource'; +export * from './EServerErrorCode'; diff --git a/webapp/packages/core/src/sdk/sdk.ts b/webapp/packages/core/src/sdk/sdk.ts index c4098afa50..c6bce13c21 100644 --- a/webapp/packages/core/src/sdk/sdk.ts +++ b/webapp/packages/core/src/sdk/sdk.ts @@ -24,6 +24,11 @@ export type AsyncTaskInfo = { status?: Maybe; error?: Maybe; result?: Maybe; + /** + * Task result. + * Can be some kind of identifier to obtain real result using another API function + */ + taskResult?: Maybe; }; export type AsyncTaskResult = SqlExecuteInfo; @@ -411,14 +416,14 @@ export type QueryMetadataGetNodeDdlArgs = { }; export type ServerConfig = { - name?: Maybe; - version?: Maybe; + name: Scalars["String"]; + version: Scalars["String"]; supportsPredefinedConnections?: Maybe; supportsProvidedConnections?: Maybe; supportsCustomConnections?: Maybe; supportsConnectionBrowser?: Maybe; supportsWorkspaces?: Maybe; - supportedLanguages?: Maybe>>; + supportedLanguages: Array>; services?: Maybe>>; productConfiguration: Scalars["Object"]; }; @@ -443,11 +448,11 @@ export type ServerMessage = { export type SessionInfo = { id: Scalars["ID"]; - createTime?: Maybe; - lastAccessTime?: Maybe; - locale?: Maybe; + createTime: Scalars["String"]; + lastAccessTime: Scalars["String"]; + locale: Scalars["String"]; serverMessages?: Maybe>>; - connections?: Maybe>>; + connections: Array>; }; export type SqlCompletionProposal = { @@ -562,6 +567,16 @@ export type CloseConnectionMutationVariables = { export type CloseConnectionMutation = Pick; +export type ConnectionStateQueryVariables = { + id: Scalars["ID"]; +}; + +export type ConnectionStateQuery = { + connection: Maybe< + Pick + >; +}; + export type CreateConnectionMutationVariables = { config: ConnectionConfig; }; @@ -1094,9 +1109,9 @@ export type OpenSessionMutationVariables = {}; export type OpenSessionMutation = { session: Maybe< - Pick & { - connections: Maybe< - Array>> + Pick & { + connections: Array< + Maybe> >; } >; @@ -1106,9 +1121,19 @@ export type ServerConfigQueryVariables = {}; export type ServerConfigQuery = { serverConfig: Maybe< - Pick & { - supportedLanguages: Maybe< - Array>> + Pick< + ServerConfig, + | "name" + | "version" + | "productConfiguration" + | "supportsPredefinedConnections" + | "supportsProvidedConnections" + | "supportsCustomConnections" + | "supportsConnectionBrowser" + | "supportsWorkspaces" + > & { + supportedLanguages: Array< + Maybe> >; } >; @@ -1118,9 +1143,9 @@ export type SessionStateQueryVariables = {}; export type SessionStateQuery = { sessionState: Maybe< - Pick & { - connections: Maybe< - Array>> + Pick & { + connections: Array< + Maybe> >; } >; @@ -1153,6 +1178,16 @@ export const CloseConnectionDocument = gql` closeConnection(id: $id) } `; +export const ConnectionStateDocument = gql` + query connectionState($id: ID!) { + connection: connectionState(id: $id) { + id + name + driverId + connected + } + } +`; export const CreateConnectionDocument = gql` mutation createConnection($config: ConnectionConfig!) { createConnection(config: $config) { @@ -1618,12 +1653,16 @@ export const UpdateResultsDataDocument = gql` export const OpenSessionDocument = gql` mutation openSession { session: openSession { + id + createTime + lastAccessTime + locale connections { id name driverId + connected } - locale } } `; @@ -1632,22 +1671,34 @@ export const ServerConfigDocument = gql` serverConfig { name version + productConfiguration + supportsPredefinedConnections + supportsProvidedConnections + supportsCustomConnections + supportsConnectionBrowser + supportsWorkspaces supportedLanguages { isoCode + displayName nativeName } + productConfiguration } } `; export const SessionStateDocument = gql` query sessionState { sessionState { + id + createTime + lastAccessTime + locale connections { id name driverId + connected } - locale } } `; @@ -1674,6 +1725,14 @@ export function getSdk(client: GraphQLClient) { variables, ); }, + connectionState( + variables: ConnectionStateQueryVariables, + ): Promise { + return client.request( + print(ConnectionStateDocument), + variables, + ); + }, createConnection( variables: CreateConnectionMutationVariables, ): Promise { diff --git a/webapp/packages/core/src/settings/ISettingsSource.ts b/webapp/packages/core/src/settings/ISettingsSource.ts new file mode 100644 index 0000000000..d3de8faa38 --- /dev/null +++ b/webapp/packages/core/src/settings/ISettingsSource.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export interface ISettingsSource { + has(key: any): boolean; + getValue(key: any): any | undefined; + setValue(key: any, value: any): void; +} diff --git a/webapp/packages/core/src/settings/SettingsService.ts b/webapp/packages/core/src/settings/SettingsService.ts index 532db0a7fa..154d294228 100644 --- a/webapp/packages/core/src/settings/SettingsService.ts +++ b/webapp/packages/core/src/settings/SettingsService.ts @@ -10,28 +10,10 @@ import { injectable } from '@dbeaver/core/di'; import { LocalStorageSaveService } from './LocalStorageSaveService'; -const PLUGIN_SETTINGS_KEY = 'pluginSettings'; - @injectable() export class SettingsService { - private properties = new Map(); - - constructor(private localStorageSaveService: LocalStorageSaveService) { - } - - /** - * - * @param name - expected in format {plugin-id}.{scope-id}.{property-name}, default scope-id = main. - * @param defaultValue - */ - getProperty(name: string, defaultValue: T): T { - if (this.properties.has(name)) { - return this.properties.get(name) as T; - } - this.properties.set(name, defaultValue); - return defaultValue; - } + constructor(private localStorageSaveService: LocalStorageSaveService) { } /** * if autosaved value exits the settings object will be populated with this value @@ -39,6 +21,6 @@ export class SettingsService { * @param settings - observable object expected */ registerSettings(settings: T, key: string): void { - this.localStorageSaveService.withAutoSave(settings, PLUGIN_SETTINGS_KEY); + this.localStorageSaveService.withAutoSave(settings, key); } } diff --git a/webapp/packages/core/src/settings/SettingsSource.ts b/webapp/packages/core/src/settings/SettingsSource.ts new file mode 100644 index 0000000000..8eb2ab9885 --- /dev/null +++ b/webapp/packages/core/src/settings/SettingsSource.ts @@ -0,0 +1,31 @@ +/* + * 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 { ISettingsSource } from './ISettingsSource'; + +export class SettingsSource implements ISettingsSource { + protected store = new Map() + + constructor(protected fallback?: ISettingsSource) {} + + has(key: string): boolean { + if (this.store.has(key)) { + return true; + } + return !!this.fallback?.has(key); + } + getValue(key: string): any | undefined { + if (this.fallback?.has(key)) { + return this.fallback.getValue(key); + } + return this.store.get(key); + } + setValue(key: string, value: any) { + this.store.set(key, value); + } +} diff --git a/webapp/packages/core/src/settings/index.ts b/webapp/packages/core/src/settings/index.ts index 621aef93fb..bc2190bf95 100644 --- a/webapp/packages/core/src/settings/index.ts +++ b/webapp/packages/core/src/settings/index.ts @@ -1,2 +1,4 @@ export * from './SettingsService'; export * from './LocalStorageSaveService'; +export * from './SettingsSource'; +export * from './ISettingsSource'; diff --git a/webapp/packages/core/src/utils/index.ts b/webapp/packages/core/src/utils/index.ts index 1bea940d58..691f2e3807 100644 --- a/webapp/packages/core/src/utils/index.ts +++ b/webapp/packages/core/src/utils/index.ts @@ -1,5 +1,6 @@ export * from './OrderedMap'; export * from './uuid'; +export * from './parseJSONFlat'; export * from './Promises/CancellablePromise'; export * from './Promises/Deferred'; diff --git a/webapp/packages/core/src/utils/parseJSONFlat.ts b/webapp/packages/core/src/utils/parseJSONFlat.ts new file mode 100644 index 0000000000..cb3cb49d89 --- /dev/null +++ b/webapp/packages/core/src/utils/parseJSONFlat.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +export function parseJSONFlat( + object: any, + setValue: (key: string, value: any) => void, + scope?: string +) { + if (typeof object === 'object') { + for (const [key, value] of Object.entries(object)) { + parseJSONFlat(value, setValue, `${scope ? `${scope}.` : ''}${key}`); + } + return; + } + if (scope) { + setValue(scope, object); + } +} diff --git a/webapp/packages/dbeaver/configs/webpack/common.js b/webapp/packages/dbeaver/configs/webpack/common.js index 5740510467..9ccdce2354 100644 --- a/webapp/packages/dbeaver/configs/webpack/common.js +++ b/webapp/packages/dbeaver/configs/webpack/common.js @@ -46,6 +46,9 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), { "@dbeaver/core/settings": path.resolve(__dirname, "../../../core/src/settings"), "@dbeaver/core/theming": path.resolve(__dirname, "../../../core/src/theming"), "@dbeaver/core/utils": path.resolve(__dirname, "../../../core/src/utils"), + "@dbeaver/core/extensions": path.resolve(__dirname, "../../../core/src/extensions"), + "@dbeaver/core/product": path.resolve(__dirname, "../../../core/src/product"), + "@dbeaver/core/plugin": path.resolve(__dirname, "../../../core/src/plugin"), // rewrite imports for unnecessary work in `web/src/libs/sdk.ts` 'graphql-tag': path.join(__dirname, '../../fix-gql.js'), diff --git a/webapp/tsconfig.json b/webapp/tsconfig.json index e3eb62edd3..a9de1f9a6b 100644 --- a/webapp/tsconfig.json +++ b/webapp/tsconfig.json @@ -16,7 +16,10 @@ "@dbeaver/core/sdk": ["packages/core/src/sdk"], "@dbeaver/core/settings": ["packages/core/src/settings"], "@dbeaver/core/theming": ["packages/core/src/theming"], - "@dbeaver/core/utils": ["packages/core/src/utils"] + "@dbeaver/core/utils": ["packages/core/src/utils"], + "@dbeaver/core/extensions": ["packages/core/src/extensions"], + "@dbeaver/core/plugin": ["packages/core/src/plugin"], + "@dbeaver/core/product": ["packages/core/src/product"] } } }