Merge branch 'devel' into CB-4356-create-smart-tabs-switchingng-for-forms

This commit is contained in:
Daria Marutkina
2023-12-15 09:15:01 +01:00
committed by GitHub
145 changed files with 3426 additions and 3125 deletions
@@ -10,7 +10,6 @@
<resource name="drivers/postgresql"/>
<resource name="drivers/clickhouse"/>
<resource name="drivers/clickhouse_com"/>
<resource name="drivers/derby"/>
<resource name="drivers/jaybird"/>
<resource name="drivers/h2"/>
<resource name="drivers/h2_v2"/>
@@ -29,7 +28,6 @@
<bundle id="drivers.postgresql" label="PostgreSQL drivers"/>
<bundle id="drivers.clickhouse" label="Clickhouse (legacy) drivers"/>
<bundle id="drivers.clickhouse_com" label="Clickhouse drivers"/>
<bundle id="drivers.derby" label="Derby drivers"/>
<bundle id="drivers.firebird" label="Firebird drivers"/>
<bundle id="drivers.h2" label="H2 drivers"/>
<bundle id="drivers.h2_v2" label="H2 v2 drivers"/>
@@ -49,7 +47,6 @@
<driver id="jaybird:jaybird"/>
<driver id="clickhouse:yandex_clickhouse"/>
<driver id="clickhouse:com_clickhouse"/>
<driver id="generic:derby_server"/>
<driver id="h2:h2_embedded"/>
<driver id="h2:h2_embedded_v2"/>
<driver id="sqlite:sqlite_jdbc"/>
@@ -95,6 +95,8 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
* In configuration mode sessions expire after a week
*/
private static final long CONFIGURATION_MODE_SESSION_IDLE_TIME = 60 * 60 * 1000 * 24 * 7;
public static final String HOST_LOCALHOST = "localhost";
public static final String HOST_127_0_0_1 = "127.0.0.1";
static {
@@ -262,9 +264,9 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
if (CommonUtils.isEmpty(localHostAddress)) {
localHostAddress = System.getProperty(CBConstants.VAR_CB_LOCAL_HOST_ADDR);
}
if (CommonUtils.isEmpty(localHostAddress) || "127.0.0.1".equals(localHostAddress) || "::0".equals(
if (CommonUtils.isEmpty(localHostAddress) || HOST_127_0_0_1.equals(localHostAddress) || "::0".equals(
localHostAddress)) {
localHostAddress = "localhost";
localHostAddress = HOST_LOCALHOST;
}
final Runtime runtime = Runtime.getRuntime();
@@ -563,7 +565,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.debug("Error resolving localhost address: " + e.getMessage());
hostName = "localhost";
hostName = HOST_LOCALHOST;
}
}
serverURL = "http://" + hostName + ":" + serverPort;
@@ -611,7 +613,7 @@ public abstract class CBApplication extends BaseWebApplication implements WebAut
}
}
parseAdditionalConfiguration(configProps);
} catch (DBException e) {
} catch (Exception e) {
throw new DBException("Error parsing server configuration", e);
}
@@ -25,4 +25,8 @@ public class CBPlatformUI extends ConsoleUserInterface {
public static final CBPlatformUI INSTANCE = new CBPlatformUI();
protected void initialize() {
// just a placeholder for injection
}
}
@@ -465,6 +465,7 @@ public class CBDatabase {
} catch (UnknownHostException e) {
hostName = "localhost";
}
byte[] hardwareAddress = RuntimeUtils.getLocalMacAddress();
String macAddress = CommonUtils.toHexString(hardwareAddress);
-40
View File
@@ -1,40 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>drivers.derby</artifactId>
<version>1.0.0</version>
<parent>
<groupId>io.cloudbeaver</groupId>
<artifactId>drivers</artifactId>
<version>1.0.0</version>
<relativePath>../</relativePath>
</parent>
<properties>
<deps.output.dir>derby</deps.output.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.15.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbytools</artifactId>
<version>10.15.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyshared</artifactId>
<version>10.15.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.15.1.3</version>
</dependency>
</dependencies>
</project>
-1
View File
@@ -15,7 +15,6 @@
<module>clickhouse_com</module>
<module>db2</module>
<module>db2-jt400</module>
<module>derby</module>
<module>h2</module>
<module>h2_v2</module>
<module>jaybird</module>
+12 -12
View File
@@ -16,17 +16,17 @@
},
"devDependencies": {
"@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.11",
"concurrently": "^8.2.1",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.10",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.2.17",
"concurrently": "^8.2.2",
"lerna": "5.5.4",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"msw": "^1.3.2",
"msw": "^2.0.10",
"path-browserify": "~1.0.1",
"prettier": "^2.8.8",
"react": "^18.2.0",
@@ -34,9 +34,9 @@
"reakit": "~1.3.11",
"reflect-metadata": "~0.1.13",
"reshadow": "^0.0.1",
"rimraf": "~5.0.1",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"rimraf": "~5.0.5",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
},
"dependencies": {},
"prettier": "@cloudbeaver/prettier-config"
@@ -32,7 +32,7 @@
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
@@ -46,8 +46,8 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"typescript": "^5.3.2"
}
}
+3 -3
View File
@@ -35,8 +35,8 @@
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -30,7 +30,7 @@
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
@@ -45,9 +45,9 @@
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"msw": "~1.x.x",
"typescript": "^5.2.2"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"msw": "~2.x.x",
"typescript": "^5.3.2"
}
}
@@ -5,18 +5,18 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { GraphQLContext, GraphQLRequest, ResponseComposition } from 'msw';
import { GraphQLResolverExtras, GraphQLResponseBody, HttpResponse, ResponseResolver } from 'msw';
import type { GetActiveUserQuery, GetActiveUserQueryVariables } from '@cloudbeaver/core-sdk';
export function mockGetActiveUser(
req: GraphQLRequest<GetActiveUserQueryVariables>,
res: ResponseComposition<GetActiveUserQuery>,
ctx: GraphQLContext<GetActiveUserQuery>,
): ReturnType<ResponseComposition<GetActiveUserQuery>> {
return res(
ctx.data({
export const mockGetActiveUser: ResponseResolver<
GraphQLResolverExtras<GetActiveUserQueryVariables>,
null,
GraphQLResponseBody<GetActiveUserQuery>
> = function mockGetActiveUser() {
return HttpResponse.json({
data: {
user: null as unknown as undefined,
}),
);
}
},
});
};
+7 -7
View File
@@ -30,7 +30,7 @@
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"go-split": "^3.4.1",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reakit": "~1.x.x",
@@ -49,12 +49,12 @@
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.25",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^14.1.2",
"@types/jest": "^29.5.10",
"@types/react": "^18.2.42",
"react": "^18.2.0",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -11,17 +11,13 @@ import type { ILoadableState } from '@cloudbeaver/core-utils';
import { getComputed } from '../getComputed';
export interface IAutoLoadable extends ILoadableState {
load: () => void;
}
export function useAutoLoad(component: { name: string }, state: IAutoLoadable | IAutoLoadable[], enabled = true, lazy = false) {
export function useAutoLoad(component: { name: string }, state: ILoadableState | ILoadableState[], enabled = true, lazy = false) {
const [loadFunctionName] = useState(`${component.name}.useAutoLoad(...)` as const);
if (!Array.isArray(state)) {
state = [state];
}
for (const loader of state as IAutoLoadable[]) {
for (const loader of state as ILoadableState[]) {
getComputed(
// activate mobx subscriptions
() => (!loader.isLoaded() || loader.isOutdated?.() === true) && !loader.isError(),
@@ -29,18 +25,20 @@ export function useAutoLoad(component: { name: string }, state: IAutoLoadable |
}
const obj = {
[loadFunctionName]: () => {
[loadFunctionName]: async () => {
if (!enabled) {
return;
}
for (const loader of state as IAutoLoadable[]) {
for (const loader of state as ILoadableState[]) {
if (loader.isError() || (loader.lazy === true && !lazy)) {
continue;
}
if (!loader.isLoaded() || loader.isOutdated?.() === true) {
loader.load();
try {
await loader.load();
} catch {}
}
}
},
+3 -3
View File
@@ -45,14 +45,14 @@
"@cloudbeaver/core-version": "~0.1.0",
"@cloudbeaver/core-version-update": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2"
"@types/react": "^18.2.42",
"typescript": "^5.3.2"
}
}
@@ -23,6 +23,6 @@
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -26,7 +26,7 @@
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2"
"@types/jest": "^29.5.10",
"typescript": "^5.3.2"
}
}
+1 -1
View File
@@ -28,6 +28,6 @@
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -3,7 +3,7 @@ const path = require('path');
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
testEnvironment: 'jsdom',
testEnvironment: require.resolve('../tests/test.environment.js'),
rootDir: path.resolve('./dist'),
moduleFileExtensions: ['js', 'jsx', 'json'],
testMatch: ['**/?(*.)+(spec|test).js?(x)'],
@@ -28,4 +28,9 @@ module.exports = {
transform: {
'\\.jsx?$': ['babel-jest', { configFile: require.resolve('./jest.babel.config.js') }],
},
testEnvironmentOptions: {
// This will force JSDOM to use the default export condition when importing msw/node, resulting in correct imports.
// https://mswjs.io/docs/migrations/1.x-to-2.x#cannot-find-module-mswnode-jsdom
customExportConditions: [''],
},
};
+42
View File
@@ -0,0 +1,42 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
declare module '*.scss?raw' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.json5' {
const json: any;
export default json;
}
declare module '*.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.m.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.css?raw' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.svg' {
const content: string;
export default content;
}
+2 -82
View File
@@ -1,84 +1,4 @@
/* eslint-disable */
/// <reference path="../../node_modules/reshadow/elements.d.ts" />
declare module '*.scss?raw' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.json5' {
const json: any;
export default json;
}
declare module '*.scss' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.m.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.css' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.css?raw' {
const classes: { readonly [key: string]: string };
export default classes;
}
declare module '*.svg' {
const content: string;
export default content;
}
declare module 'reshadow' {
import { createElement, Component } from 'react';
type StylesMap = { [key: string]: any };
type IdFunction = <A extends JSX.Element>(node: A) => A;
declare function taggedStyled(strs: TemplateStringsArray, ...values: any[]): IdFunction;
declare function taggedStyled<A extends JSX.Element>(node: A): A;
/**
*
* @deprecated use css-modules instead
*/
declare function styled(...styles: StylesMap[]): typeof taggedStyled;
/**
*
* @deprecated use css-modules instead
*/
declare function styled(strs: TemplateStringsArray, ...values: any[]): IdFunction;
/**
*
* @deprecated use css-modules instead
*/
export declare var use: ((value: StylesMap) => {}) & {
[key: string]: typeof Component;
};
/**
*
* @deprecated use css-modules instead
*/
export declare function css(strs: TemplateStringsArray, ...values: any[]): StylesMap;
export declare function keyframes(strs: TemplateStringsArray, ...values: any[]): string;
/**
*
* @deprecated use css-modules instead
*/
export declare function create(styles: StylesMap[]): StylesMap;
declare var jsx: typeof createElement;
export default styled;
}
/// <reference path="./msw.d.ts" />
/// <reference path="./css-modules.d.ts" />
+24
View File
@@ -0,0 +1,24 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { GraphQLError } from 'graphql';
import { DefaultBodyType, GraphQLVariables } from 'msw';
// types are not exported: https://github.com/mswjs/msw/pull/1826
declare module 'msw' {
export type GraphQLResolverExtras<Variables extends GraphQLVariables> = {
query: string;
operationName: string;
variables: Variables;
cookies: Record<string, string>;
};
export interface GraphQLResponseBody<BodyType extends DefaultBodyType> {
data?: BodyType | null;
errors?: readonly Partial<GraphQLError>[] | null;
}
}
@@ -0,0 +1,16 @@
const Environment = require('jest-environment-jsdom').default;
/**
JSDOM does not have an implementation for TextDecoder / TextEncoder present on their globals
and therefore not only are they not found, but you can't add them without access to their global context inside the VM.
MSW 2.0 related issue.
*/
module.exports = class CustomTestEnvironment extends Environment {
async setup() {
await super.setup();
this.global.TextEncoder = TextEncoder;
this.global.TextDecoder = TextDecoder;
this.global.Response = Response;
this.global.Request = Request;
}
};
@@ -33,11 +33,11 @@
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -19,11 +19,11 @@
"dependencies": {
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+4 -4
View File
@@ -19,13 +19,13 @@
},
"dependencies": {
"@cloudbeaver/core-executor": "~0.1.0",
"inversify": "^6.0.1",
"inversify": "^6.0.2",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"@types/jest": "^29.5.5"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"@types/jest": "^29.5.10"
}
}
+2 -2
View File
@@ -20,10 +20,10 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-localization": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+4 -4
View File
@@ -23,7 +23,7 @@
"@cloudbeaver/core-plugin": "~0.1.0",
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
@@ -32,8 +32,8 @@
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"typescript": "^5.3.2"
}
}
@@ -194,8 +194,6 @@ export class NotificationService {
isSilent: silent,
});
}
console.error(exception);
}
throwSilently(exception: Error | GQLError | undefined | null): void {
+2 -2
View File
@@ -18,10 +18,10 @@
"update-ts-references": "rimraf --glob dist && typescript-resolve-references"
},
"dependencies": {
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+1 -1
View File
@@ -19,6 +19,6 @@
"dependencies": {},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -25,7 +25,7 @@
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-settings": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
@@ -37,8 +37,8 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"typescript": "^5.3.2"
}
}
@@ -108,6 +108,7 @@ export default [
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['ui_disable', 'Disable'],
['ui_readonly', 'Read-only'],
['ui_test', 'Test'],
['root_permission_denied', "You don't have permissions"],
@@ -92,6 +92,7 @@ export default [
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['ui_disable', 'Disable'],
['ui_readonly', 'In sola lettura'],
['ui_test', 'Test'],
['root_permission_denied', 'Non hai i permessi'],
@@ -104,6 +104,7 @@ export default [
['ui_upload_file_fail', 'Не удалось загрузить файл'],
['ui_filter', 'Фильтр'],
['ui_disable', 'Отключить'],
['ui_readonly', 'Доступно только для чтения'],
['ui_test', 'Проверить'],
['root_permission_denied', 'Отказано в доступе'],
@@ -105,6 +105,7 @@ export default [
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['ui_disable', 'Disable'],
['ui_readonly', '只读'],
['ui_test', 'Test'],
['root_permission_denied', '您没有权限'],
@@ -31,7 +31,7 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
@@ -51,8 +51,8 @@
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"typescript": "^5.3.2"
}
}
@@ -21,7 +21,7 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-dialogs": "~0.1.0",
"@cloudbeaver/core-events": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reakit": "~1.x.x",
@@ -29,7 +29,7 @@
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2"
"@types/react": "^18.2.42",
"typescript": "^5.3.2"
}
}
+1 -1
View File
@@ -23,6 +23,6 @@
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+2 -2
View File
@@ -27,10 +27,10 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -27,10 +27,10 @@
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+4 -4
View File
@@ -21,12 +21,12 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-executor": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"devDependencies": {
"@types/jest": "^29.5.5",
"mobx": "^6.10.2",
"typescript": "^5.2.2"
"@types/jest": "^29.5.10",
"mobx": "^6.12.0",
"typescript": "^5.3.2"
},
"peerDependencies": {}
}
+4 -4
View File
@@ -23,14 +23,14 @@
"@cloudbeaver/core-sdk": "~0.1.0",
"@cloudbeaver/core-settings": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"rxjs": "^7.8.1"
},
"peerDependencies": {},
"devDependencies": {
"@cloudbeaver/core-sdk": "~0.1.0",
"@types/jest": "^29.5.5",
"msw": "~1.x.x",
"typescript": "^5.2.2"
"@types/jest": "^29.5.10",
"msw": "~2.x.x",
"typescript": "^5.3.2"
}
}
@@ -7,11 +7,8 @@
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { EServerErrorCode, GQLError, GraphQLService } from '@cloudbeaver/core-sdk';
import { EServerErrorCode, GQLError, GraphQLService, SessionError } from '@cloudbeaver/core-sdk';
import { errorOf } from '@cloudbeaver/core-utils';
import { SessionError } from './SessionError';
@injectable()
export class SessionExpireService extends Bootstrap {
expired = false;
@@ -5,9 +5,9 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { rest } from 'msw';
import { http, HttpResponse } from 'msw';
// TODO: use new msw API https://github.com/mswjs/msw/issues/156
export function createWebsocketEndpoint(): ReturnType<typeof rest.get> {
return rest.get('http://localhost/api/ws', (req, res, ctx) => res(ctx.status(404)));
export function createWebsocketEndpoint(): ReturnType<typeof http.get> {
return http.get('http://localhost/api/ws', () => new HttpResponse(null, { status: 404 }));
}
@@ -5,12 +5,12 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { GraphQLRequest, RequestHandler } from 'msw';
import type { GraphQLHandler } from 'msw';
import { setupServer } from 'msw/node';
import { createWebsocketEndpoint } from './createWebsocketEndpoint';
export function mockGraphQL(...requestHandlers: RequestHandler<any, GraphQLRequest<any>, any, GraphQLRequest<any>>[]) {
export function mockGraphQL(...requestHandlers: GraphQLHandler[]) {
const server = setupServer(...requestHandlers, createWebsocketEndpoint());
beforeAll(() => server.listen());
@@ -5,27 +5,28 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { GraphQLContext, GraphQLRequest, ResponseComposition } from 'msw';
import { GraphQLResolverExtras, GraphQLResponseBody, HttpResponse, ResponseResolver } from 'msw';
import type { OpenSessionMutation, OpenSessionMutationVariables } from '@cloudbeaver/core-sdk';
export function mockOpenSession(
req: GraphQLRequest<OpenSessionMutationVariables>,
res: ResponseComposition<OpenSessionMutation>,
ctx: GraphQLContext<OpenSessionMutation>,
) {
export const mockOpenSession: ResponseResolver<
GraphQLResolverExtras<OpenSessionMutationVariables>,
null,
GraphQLResponseBody<OpenSessionMutation>
> = function mockOpenSession(info) {
const date = new Date().toISOString();
return res(
ctx.data({
return HttpResponse.json({
data: {
session: {
valid: true,
remainingTime: 0,
createTime: date,
lastAccessTime: date,
cacheExpired: false,
locale: req.variables.defaultLocale ?? 'en',
locale: info.variables.defaultLocale ?? 'en',
actionParameters: null,
},
}),
);
}
},
});
};
@@ -5,18 +5,16 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { GraphQLContext, GraphQLRequest, ResponseComposition } from 'msw';
import { GraphQLResolverExtras, GraphQLResponseBody, HttpResponse, ResponseResolver } from 'msw';
import type { ServerConfigQuery, ServerConfigQueryVariables } from '@cloudbeaver/core-sdk';
import { defaultServerConfig } from '../data/defaultServerConfig';
export function mockServerConfig(productConfiguration?: Record<string, any>) {
return function mockServerConfig(
req: GraphQLRequest<ServerConfigQueryVariables>,
res: ResponseComposition<ServerConfigQuery>,
ctx: GraphQLContext<ServerConfigQuery>,
) {
return res(ctx.data(defaultServerConfig(productConfiguration)));
export function mockServerConfig(
productConfiguration?: Record<string, any>,
): ResponseResolver<GraphQLResolverExtras<ServerConfigQueryVariables>, null, GraphQLResponseBody<ServerConfigQuery>> {
return function mockServerConfig() {
return HttpResponse.json({ data: defaultServerConfig(productConfiguration) });
};
}
+2 -2
View File
@@ -20,12 +20,12 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-executor": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"router5": "^8.0.1",
"router5-plugin-browser": "^8.0.1"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+6 -6
View File
@@ -23,21 +23,21 @@
"dependencies": {
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"axios": "^1.6.0",
"axios": "^1.6.2",
"graphql-request": "^6.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"devDependencies": {
"@graphql-codegen/add": "~5.0.0",
"@graphql-codegen/cli": "~5.0.0",
"@graphql-codegen/near-operation-file-preset": "~2.5.0",
"@graphql-codegen/near-operation-file-preset": "~3.0.0",
"@graphql-codegen/typescript": "~4.0.1",
"@graphql-codegen/typescript-graphql-files-modules": "~2.2.1",
"@graphql-codegen/typescript-graphql-request": "~5.0.0",
"@graphql-codegen/typescript-graphql-files-modules": "~3.0.0",
"@graphql-codegen/typescript-graphql-request": "~6.0.1",
"@graphql-codegen/typescript-operations": "~4.0.1",
"@parcel/watcher": "~2.3.0",
"graphql": "^16.8.1",
"typescript": "^5.2.2"
"typescript": "^5.3.2"
},
"peerDependencies": {}
}
+1
View File
@@ -13,4 +13,5 @@ export * from './isObjectPropertyInfoStateEqual';
export * from './sdk';
export * from './ServerInternalError';
export * from './ServiceError';
export * from './SessionError';
export * from './manifest';
+2 -2
View File
@@ -21,10 +21,10 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-executor": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
@@ -19,11 +19,11 @@
"dependencies": {
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"@cloudbeaver/core-executor": "~0.1.0"
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+5 -5
View File
@@ -25,7 +25,7 @@
"@cloudbeaver/core-plugin": "~0.1.0",
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-settings": "~0.1.0",
"mobx": "^6.10.2"
"mobx": "^6.12.0"
},
"peerDependencies": {},
"devDependencies": {
@@ -48,9 +48,9 @@
"@material/switch": "~4.0.0",
"@material/theme": "~4.0.0",
"@material/typography": "~4.0.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
+4 -4
View File
@@ -29,7 +29,7 @@
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"react-dnd": "^16.0.1",
@@ -39,8 +39,8 @@
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
+7 -7
View File
@@ -18,20 +18,20 @@
"update-ts-references": "rimraf --glob dist && typescript-resolve-references"
},
"dependencies": {
"@timohausmann/quadtree-ts": "^2.0.0-beta.1",
"@timohausmann/quadtree-ts": "2.0.0-beta.1",
"clsx": "^2.0.0",
"fast-deep-equal": "^3.1.3",
"md5": "^2.3.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"underscore": "^1.13.6",
"uuid": "^9.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/md5": "~2.3.2",
"@types/uuid": "~9.0.3",
"@types/underscore": "^1.11.9",
"typescript": "^5.2.2",
"@types/jest": "^29.5.5"
"@types/md5": "~2.3.5",
"@types/uuid": "~9.0.7",
"@types/underscore": "^1.11.15",
"typescript": "^5.3.2",
"@types/jest": "^29.5.10"
}
}
@@ -20,12 +20,12 @@
"@cloudbeaver/core-di": "~0.1.0",
"@cloudbeaver/core-localization": "~0.1.0",
"@cloudbeaver/core-version": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"semver": "^7.5.4"
},
"peerDependencies": {},
"devDependencies": {
"@types/semver": "~7.5.1",
"typescript": "^5.2.2"
"@types/semver": "~7.5.6",
"typescript": "^5.3.2"
}
}
+3 -3
View File
@@ -22,12 +22,12 @@
"@cloudbeaver/core-resource": "~0.1.0",
"@cloudbeaver/core-root": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2"
"@types/react": "^18.2.42",
"typescript": "^5.3.2"
}
}
+3 -3
View File
@@ -24,7 +24,7 @@
"@cloudbeaver/core-localization": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
@@ -32,7 +32,7 @@
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2"
"@types/react": "^18.2.42",
"typescript": "^5.3.2"
}
}
+7 -7
View File
@@ -12,15 +12,15 @@
],
"type": "commonjs",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "~6.7.2",
"@typescript-eslint/parser": "~6.7.2",
"eslint": "~8.49.0",
"@typescript-eslint/eslint-plugin": "~6.13.2",
"@typescript-eslint/parser": "~6.13.2",
"eslint": "~8.55.0",
"eslint-config-airbnb-base": "~15.0.0",
"eslint-config-airbnb-typescript": "~17.1.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "~2.28.1",
"eslint-plugin-jest": "~27.4.0",
"eslint-plugin-jsx-a11y": "~6.7.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "~2.29.0",
"eslint-plugin-jest": "~27.6.0",
"eslint-plugin-jsx-a11y": "~6.8.0",
"eslint-plugin-node": "~11.1.0",
"eslint-plugin-promise": "~6.1.1",
"eslint-plugin-react": "~7.33.2",
@@ -34,7 +34,7 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-settings-menu": "~0.1.0",
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1",
@@ -42,8 +42,8 @@
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -36,15 +36,15 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-administration": "~0.1.0",
"@cloudbeaver/plugin-authentication": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -34,15 +34,15 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-administration": "~0.1.0",
"@cloudbeaver/plugin-settings-menu": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -37,6 +37,7 @@ import { AuthProviderForm } from './AuthProviderForm/AuthProviderForm';
import { ConfigurationsList } from './AuthProviderForm/ConfigurationsList';
import { FEDERATED_AUTH } from './FEDERATED_AUTH';
import { getAuthProviderTabId, useAuthDialogState } from './useAuthDialogState';
import { NotificationService } from '@cloudbeaver/core-events';
export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function AuthDialog({
payload: { providerId, configurationId, linkUser = false, accessRequest = false },
@@ -49,6 +50,7 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
const authenticationService = useService(AuthenticationService);
const userInfo = useService(UserInfoResource);
const commonDialogService = useService(CommonDialogService);
const notificationService = useService(NotificationService);
const translate = useTranslate();
const state = dialogData.state;
@@ -86,8 +88,10 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
}
async function login(linkUser: boolean, provider?: AuthProvider, configuration?: AuthProviderConfiguration) {
await dialogData.login(linkUser, provider, configuration);
rejectDialog();
try {
await dialogData.login(linkUser, provider, configuration);
rejectDialog();
} catch {}
}
function navToSettings() {
+1 -1
View File
@@ -25,6 +25,6 @@
},
"peerDependencies": {},
"devDependencies": {
"typescript": "^5.2.2"
"typescript": "^5.3.2"
}
}
+12 -12
View File
@@ -20,19 +20,19 @@
"@cloudbeaver/core-blocks": "~0.1.0",
"@cloudbeaver/core-theming": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"@codemirror/autocomplete": "6.9.0",
"@codemirror/commands": "6.2.5",
"@codemirror/lang-html": "6.4.6",
"@codemirror/autocomplete": "6.11.1",
"@codemirror/commands": "6.3.2",
"@codemirror/lang-html": "6.4.7",
"@codemirror/lang-javascript": "6.2.1",
"@codemirror/lang-json": "6.0.1",
"@codemirror/lang-sql": "6.5.4",
"@codemirror/lang-xml": "6.0.2",
"@codemirror/language": "6.9.0",
"@codemirror/merge": "6.1.2",
"@codemirror/search": "6.5.2",
"@codemirror/state": "6.2.1",
"@codemirror/view": "6.18.0",
"@lezer/highlight": "^1.1.6",
"@codemirror/language": "6.9.3",
"@codemirror/merge": "6.3.1",
"@codemirror/search": "6.5.5",
"@codemirror/state": "6.3.3",
"@codemirror/view": "6.22.1",
"@lezer/highlight": "^1.2.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -41,8 +41,8 @@
"peerDependencies": {},
"devDependencies": {
"@cloudbeaver/core-theming": "~0.1.0",
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -7,9 +7,8 @@
*/
import { observer } from 'mobx-react-lite';
import { forwardRef } from 'react';
import styled from 'reshadow';
import { clsx } from '@cloudbeaver/core-utils';
import { s, useS } from '@cloudbeaver/core-blocks';
import type { IEditorProps } from './IEditorProps';
import type { IEditorRef } from './IEditorRef';
@@ -41,9 +40,9 @@ export const Editor = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
},
ref,
) {
useS(EDITOR_BASE_STYLES);
extensions = useCodemirrorExtensions(extensions);
const defaultExtensions = useEditorDefaultExtensions({
lineNumbers,
tooltips,
@@ -64,10 +63,11 @@ export const Editor = observer<IEditorProps & IDefaultExtensions, IEditorRef>(
extensions.set(...defaultExtensions);
return styled(EDITOR_BASE_STYLES)(
<wrapper className={clsx('editor', rest.className)}>
return (
// all styles is global scoped so we can't get them from module
<div className={s({ editor: 'editor' }, { editor: true }, rest.className)}>
<ReactCodemirror {...rest} ref={ref} extensions={extensions} />
</wrapper>,
</div>
);
}),
);
@@ -0,0 +1,37 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { RangeSet, RangeValue } from '@codemirror/state';
import type { IHyperlinkInfo } from './IHyperlinkInfo';
export const enum HyperlinkState {
Inactive,
Pending,
Result,
}
export type HyperlinkSet = RangeSet<Hyperlink>;
export class Hyperlink extends RangeValue {
hyperlink: IHyperlinkInfo | null;
state: HyperlinkState;
constructor() {
super();
this.hyperlink = null;
this.state = HyperlinkState.Inactive;
}
static create() {
return new Hyperlink();
}
static get none(): HyperlinkSet {
return RangeSet.of<Hyperlink>([]);
}
}
@@ -0,0 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 type { SelectionRange } from '@codemirror/state';
import type { IHyperlinkInfo } from './IHyperlinkInfo';
export type HyperlinkLoader = (pos: SelectionRange) => Promise<IHyperlinkInfo | null>;
@@ -0,0 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 IHyperlinkInfo {
tooltip?: string;
onClick: () => void;
}
@@ -0,0 +1,238 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { combineConfig, Compartment, EditorState, Extension, Facet, Range, SelectionRange, StateEffect, StateField } from '@codemirror/state';
import { Decoration, EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
import { Hyperlink, HyperlinkState } from './Hyperlink';
import type { HyperlinkLoader } from './HyperlinkLoader';
import type { IHyperlinkInfo } from './IHyperlinkInfo';
export interface HyperlinksConfig {
loadLinkInfo: HyperlinkLoader;
}
export const hyperlinksConfig = Facet.define<HyperlinksConfig, Required<HyperlinksConfig>>({
combine(configs) {
return combineConfig<Required<HyperlinksConfig>>(configs, {
loadLinkInfo: () => Promise.resolve(null),
});
},
});
const metaKeyStateEffect = StateEffect.define<boolean>();
const hoverPositionEffect = StateEffect.define<number | null>();
const addHyperlinkEffect = StateEffect.define<Range<Hyperlink>>();
const updateHyperlinkEffect = StateEffect.define<[Hyperlink, IHyperlinkInfo | null]>();
const modKeyState = StateField.define<boolean>({
create: () => false,
update(value, tr) {
for (const effect of tr.effects) {
if (effect.is(metaKeyStateEffect)) {
return effect.value;
}
}
return value;
},
});
const hoverPositionState = StateField.define<number | null>({
create: () => null,
update(value, tr) {
for (const effect of tr.effects) {
if (effect.is(hoverPositionEffect)) {
return effect.value;
}
}
return value;
},
});
const linksState = StateField.define({
create: () => Hyperlink.none,
update(hyperlinks, tr) {
hyperlinks = hyperlinks.map(tr.changes);
const isMetaKey = tr.state.field(modKeyState);
if (!isMetaKey) {
return hyperlinks.size === 0 ? hyperlinks : Hyperlink.none;
}
for (const effect of tr.effects) {
if (effect.is(addHyperlinkEffect)) {
return hyperlinks.update({
add: [effect.value],
});
}
if (effect.is(updateHyperlinkEffect)) {
return hyperlinks.update({
filter: (from, to, hyperlink) => {
if (hyperlink === effect.value[0]) {
hyperlink.state = HyperlinkState.Result;
hyperlink.hyperlink = effect.value[1];
}
return true;
},
});
}
}
return hyperlinks;
},
provide: linksState =>
EditorView.decorations.compute([linksState, hoverPositionState], state => {
const hoverCursor = state.field(hoverPositionState);
const hyperlinks = state.field(linksState).update({ filter: (from, to) => !!hoverCursor && from <= hoverCursor && hoverCursor <= to });
const iter = hyperlinks.iter();
const decorations = [];
while (iter.value) {
const hyperlink = iter.value;
const link = hyperlink.hyperlink;
if (link) {
decorations.push(
Decoration.mark({
class: 'cm-link cm-link-loaded',
attributes: { title: link.tooltip || '' },
link,
}).range(iter.from, iter.to),
);
} else {
decorations.push(
Decoration.mark({
class: 'cm-link cm-link-loading',
}).range(iter.from, iter.to),
);
}
iter.next();
}
return Decoration.set(decorations);
}),
});
const hyperlinkPlugin = ViewPlugin.fromClass(
class {
private pendingUpdate: ReturnType<typeof setTimeout> | null = null;
constructor(public view: EditorView) {}
update(update: ViewUpdate) {
const isMetaKey = update.state.field(modKeyState);
if (isMetaKey) {
this.scheduleLinkInfoLoad();
} else {
this.resetLinkInfoLoad();
}
}
private scheduleLinkInfoLoad() {
this.resetLinkInfoLoad();
this.pendingUpdate = setTimeout(() => {
this.pendingUpdate = null;
const position = this.getHoveredWordPosition(this.view.state);
if (position === null) {
return;
}
const links = this.view.state.field(linksState);
const config = this.view.state.facet(hyperlinksConfig);
let hyperlink: Hyperlink | null = null;
links.between(position.from, position.to, (from, to, h) => {
if (from === position.from && to === position.to) {
hyperlink = h;
}
});
if (!hyperlink) {
hyperlink = Hyperlink.create();
this.view.dispatch({ effects: addHyperlinkEffect.of(hyperlink!.range(position.from, position.to)) });
}
if (hyperlink.state === HyperlinkState.Inactive) {
hyperlink.state = HyperlinkState.Pending;
config
.loadLinkInfo(position)
.then(link => {
this.view.dispatch({ effects: updateHyperlinkEffect.of([hyperlink!, link]) });
})
.catch(() => {
this.view.dispatch({ effects: updateHyperlinkEffect.of([hyperlink!, null]) });
});
}
}, 100);
}
private resetLinkInfoLoad() {
if (this.pendingUpdate) {
clearTimeout(this.pendingUpdate);
this.pendingUpdate = null;
}
}
private getHoveredWordPosition(state: EditorState): SelectionRange | null {
const position = state.field(hoverPositionState);
if (position === null) {
return null;
}
return state.wordAt(position);
}
},
{
eventHandlers: {
keydown: (event, view) => {
if (event.key === 'Meta') {
view.dispatch({ effects: [metaKeyStateEffect.of(true)] });
}
},
keyup: (event, view) => {
if (event.key === 'Meta') {
view.dispatch({ effects: metaKeyStateEffect.of(false) });
}
},
mousedown(event, view) {
try {
const linksSet = view.state.field(linksState);
const target = event.target as HTMLElement;
const pos = view.posAtDOM(target);
const iterator = linksSet.iter(pos);
const hyperlink = iterator.value;
if (hyperlink) {
const link = hyperlink.hyperlink;
if (link) {
link.onClick();
}
}
} catch {}
},
mousemove: (event, view) => {
const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
view.dispatch({ effects: [hoverPositionEffect.of(pos), metaKeyStateEffect.of(event.metaKey)] });
},
},
},
);
const EDITOR_AUTOCOMPLETION_COMPARTMENT = new Compartment();
export function useHyperlink(config: HyperlinksConfig): [Compartment, Extension] {
return [EDITOR_AUTOCOMPLETION_COMPARTMENT, [hyperlinkPlugin, modKeyState, hoverPositionState, linksState, hyperlinksConfig.of(config)]];
}
@@ -8,6 +8,10 @@ export * from './useEditorDefaultExtensions';
export * from './useEditorAutocompletion';
export * from './useCodemirrorExtensions';
export * from './Hyperlink/HyperlinkLoader';
export * from './Hyperlink/IHyperlinkInfo';
export * from './Hyperlink/useHyperlink';
export * from '@codemirror/view';
export * from '@codemirror/state';
export * from '@codemirror/autocomplete';
@@ -48,6 +48,14 @@
border-left-color: $cursor;
}
.editor .cm-link {
&.cm-link-loaded {
@include mdc-theme-prop(color, primary, false);
text-decoration: underline;
cursor: pointer;
}
}
.editor .cm-dropCursor {
border-left: 1.2px solid $cursor;
}
@@ -7,17 +7,20 @@
*/
import type { ThemeSelector } from '@cloudbeaver/core-theming';
import type Dark from './dark.module.scss';
import type Light from './light.module.scss';
export const EDITOR_BASE_STYLES: ThemeSelector = async theme => {
let styles: any;
let styles: typeof Light & typeof Dark;
switch (theme) {
case 'dark':
styles = await import('./dark.module.scss');
styles = (await import('./dark.module.scss')).default;
break;
default:
styles = await import('./light.module.scss');
styles = (await import('./light.module.scss')).default;
break;
}
return styles.default;
return styles;
};
@@ -29,14 +29,14 @@
"@cloudbeaver/core-utils": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-connections": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -33,14 +33,14 @@
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-connections": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2"
"@types/react": "^18.2.42",
"typescript": "^5.3.2"
}
}
@@ -31,15 +31,15 @@
"@cloudbeaver/core-utils": "~0.1.0",
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-connections": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -1,267 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { computed, makeObservable, observable } from 'mobx';
import { ErrorDetailsDialog } from '@cloudbeaver/core-blocks';
import {
Connection,
ConnectionInfoProjectKey,
ConnectionInfoResource,
ConnectionInitConfig,
createConnectionParam,
DatabaseAuthModelsResource,
DBDriver,
DBDriverResource,
USER_NAME_PROPERTY_ID,
} from '@cloudbeaver/core-connections';
import { IDestructibleController, IInitializableController, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
import { ProjectsService } from '@cloudbeaver/core-projects';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { DatabaseAuthModel, DetailsError, NetworkHandlerAuthType } from '@cloudbeaver/core-sdk';
import { errorOf, getUniqueName } from '@cloudbeaver/core-utils';
import type { IConnectionAuthenticationConfig } from '@cloudbeaver/plugin-connections';
import { TemplateConnectionsResource } from '../TemplateConnectionsResource';
import { TemplateConnectionsService } from '../TemplateConnectionsService';
export enum ConnectionStep {
ConnectionTemplateSelect,
Connection,
}
export interface IConnectionController {
template: Connection | null;
config: IConnectionAuthenticationConfig;
isConnecting: boolean;
onConnect: () => void;
}
@injectable()
export class ConnectionController implements IInitializableController, IDestructibleController, IConnectionController {
step = ConnectionStep.ConnectionTemplateSelect;
isLoading = true;
isConnecting = false;
template: Connection | null = null;
authModel?: DatabaseAuthModel;
config: IConnectionAuthenticationConfig = {
credentials: {},
networkHandlersConfig: [],
saveCredentials: false,
};
hasDetails = false;
responseMessage: string | null = null;
private exception: DetailsError | null = null;
private onClose!: () => void;
private isDistructed = false;
get templateConnections(): Connection[] {
return this.templateConnectionsService.projectTemplates;
}
get dbDrivers(): Map<string, DBDriver> {
return this.dbDriverResource.data;
}
get dbDriver(): DBDriver | undefined {
if (!this.template) {
return undefined;
}
return this.dbDrivers.get(this.template.driverId);
}
get networkHandlers(): string[] {
if (!this.template?.networkHandlersConfig) {
return [];
}
return this.template.networkHandlersConfig.filter(handler => handler.enabled && !handler.savePassword).map(handler => handler.id);
}
constructor(
private readonly dbDriverResource: DBDriverResource,
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly templateConnectionsResource: TemplateConnectionsResource,
private readonly templateConnectionsService: TemplateConnectionsService,
private readonly notificationService: NotificationService,
private readonly commonDialogService: CommonDialogService,
private readonly dbAuthModelsResource: DatabaseAuthModelsResource,
private readonly projectsService: ProjectsService,
) {
this.authModel = undefined;
makeObservable(this, {
step: observable,
isLoading: observable,
isConnecting: observable,
template: observable,
authModel: observable,
config: observable,
hasDetails: observable,
responseMessage: observable,
networkHandlers: computed,
});
}
init(onClose: () => void): void {
this.onClose = onClose;
this.loadTemplateConnections();
}
destruct(): void {
this.isDistructed = true;
}
onStep = (step: ConnectionStep): void => {
this.step = step;
this.clearError();
if (step === ConnectionStep.ConnectionTemplateSelect) {
this.template = null;
}
};
onConnect = async (): Promise<void> => {
if (!this.template || !this.projectsService.userProject) {
return;
}
this.isConnecting = true;
this.clearError();
try {
const connections = await this.connectionInfoResource.load(ConnectionInfoProjectKey(this.projectsService.userProject.id));
const connectionNames = connections.map(connection => connection.name);
const uniqueConnectionName = getUniqueName(this.template.name || 'Template connection', connectionNames);
const connection = await this.connectionInfoResource.createFromTemplate(this.template.projectId, this.template.id, uniqueConnectionName);
try {
await this.connectionInfoResource.init(this.getConfig(connection.projectId, connection.id));
this.notificationService.logSuccess({ title: 'Connection is established', message: connection.name });
this.onClose();
} catch (exception: any) {
this.showError(exception, 'Failed to establish connection');
await this.connectionInfoResource.deleteConnection(createConnectionParam(connection));
}
} catch (exception: any) {
this.showError(exception, 'Failed to establish connection');
} finally {
this.isConnecting = false;
}
};
onTemplateSelect = async (templateId: string): Promise<void> => {
this.template = this.templateConnections.find(template => template.id === templateId)!;
await this.loadAuthModel();
this.clearError();
this.config = {
credentials: {},
networkHandlersConfig: [],
saveCredentials: false,
};
if (this.template.authNeeded) {
const property = this.template.authProperties?.find(property => property.id === USER_NAME_PROPERTY_ID);
if (property?.value) {
this.config.credentials[USER_NAME_PROPERTY_ID] = property.value;
}
}
for (const id of this.networkHandlers) {
const handler = this.template.networkHandlersConfig?.find(handler => handler.id === id);
if (handler && (handler.userName || handler.authType !== NetworkHandlerAuthType.Password)) {
this.config.networkHandlersConfig.push({
id: handler.id,
authType: handler.authType,
userName: handler.userName,
password: handler.password,
savePassword: handler.savePassword,
});
}
}
this.step = ConnectionStep.Connection;
if (!this.authModel) {
this.onConnect();
}
};
onShowDetails = (): void => {
if (this.exception) {
this.commonDialogService.open(ErrorDetailsDialog, this.exception);
}
};
private getConfig(projectId: string, connectionId: string) {
const config: ConnectionInitConfig = {
projectId,
connectionId,
};
if (Object.keys(this.config.credentials).length > 0) {
config.credentials = this.config.credentials;
config.saveCredentials = this.config.saveCredentials;
}
if (this.config.networkHandlersConfig.length > 0) {
config.networkCredentials = this.config.networkHandlersConfig;
}
return config;
}
private clearError() {
this.responseMessage = null;
this.hasDetails = false;
this.exception = null;
}
private showError(exception: Error, message: string) {
const detailsError = errorOf(exception, DetailsError);
if (detailsError && !this.isDistructed) {
this.responseMessage = detailsError.message;
this.hasDetails = detailsError.hasDetails();
this.exception = detailsError;
} else {
this.notificationService.logException(exception, message);
}
}
private async loadTemplateConnections() {
try {
await this.templateConnectionsResource.load();
await this.dbDriverResource.load(CachedMapAllKey);
} catch (exception: any) {
this.notificationService.logException(exception, "Can't load database sources");
} finally {
this.isLoading = false;
}
}
private async loadAuthModel() {
if (!this.dbDriver || this.dbDriver.anonymousAccess) {
return;
}
try {
this.isLoading = true;
this.authModel = await this.dbAuthModelsResource.load(this.dbDriver.defaultAuthModel);
} catch (exception: any) {
this.notificationService.logException(exception, "Can't load driver auth model");
} finally {
this.isLoading = false;
}
}
}
@@ -14,85 +14,76 @@ import {
CommonDialogWrapper,
ErrorMessage,
Form,
Loader,
s,
useAdministrationSettings,
useErrorDetails,
useFocus,
useS,
useTranslate,
} from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import type { DialogComponent } from '@cloudbeaver/core-dialogs';
import { ConnectionAuthenticationFormLoader } from '@cloudbeaver/plugin-connections';
import { ConnectionController, ConnectionStep } from './ConnectionController';
import style from './ConnectionDialog.m.css';
import { ConnectionDialogFooter } from './ConnectionDialogFooter';
import { ConnectionStep } from './EConnectionStep';
import { TemplateConnectionSelector } from './TemplateConnectionSelector/TemplateConnectionSelector';
import { useConnectionDialog } from './useConnectionDialog';
export const ConnectionDialog: DialogComponent<null, null> = observer(function ConnectionDialog({ rejectDialog }) {
const styles = useS(style);
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const controller = useController(ConnectionController, rejectDialog);
const translate = useTranslate();
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const { credentialsSavingEnabled } = useAdministrationSettings();
const dialog = useConnectionDialog(rejectDialog);
const errorDetails = useErrorDetails(dialog.connectException);
let subtitle: string | undefined;
if (controller.step === ConnectionStep.Connection && controller.template?.name) {
subtitle = controller.template.name;
}
const subTitle = dialog.step === ConnectionStep.Connection ? dialog.template?.name : undefined;
return (
<CommonDialogWrapper size="large" fixedSize>
<CommonDialogWrapper size="large" fixedSize={dialog.step === ConnectionStep.ConnectionTemplateSelect}>
<CommonDialogHeader
title="basicConnection_connectionDialog_newConnection"
subTitle={subtitle}
icon={controller.dbDriver?.icon}
subTitle={subTitle}
icon={dialog.driver?.icon}
onReject={rejectDialog}
/>
<CommonDialogBody noBodyPadding={controller.step === ConnectionStep.ConnectionTemplateSelect} noOverflow>
{controller.isLoading && <Loader />}
{!controller.isLoading && controller.step === ConnectionStep.ConnectionTemplateSelect && (
<TemplateConnectionSelector
templateConnections={controller.templateConnections}
dbDrivers={controller.dbDrivers}
onSelect={controller.onTemplateSelect}
/>
)}
{controller.step === ConnectionStep.Connection &&
(!controller.authModel ? (
<CommonDialogBody noBodyPadding={dialog.step === ConnectionStep.ConnectionTemplateSelect} noOverflow>
{dialog.step === ConnectionStep.ConnectionTemplateSelect && <TemplateConnectionSelector onSelect={dialog.selectTemplate} />}
{dialog.step === ConnectionStep.Connection &&
(!dialog.authModelId ? (
<center className={s(styles, { center: true })}>
{controller.isConnecting && translate('basicConnection_connectionDialog_connecting_message')}
{dialog.processing && translate('basicConnection_connectionDialog_connecting_message')}
</center>
) : (
<Form ref={focusedRef} className={s(styles, { submittingForm: true })} onSubmit={controller.onConnect}>
<Form ref={focusedRef} className={s(styles, { submittingForm: true })} onSubmit={dialog.connect}>
<ConnectionAuthenticationFormLoader
config={controller.config}
authModelId={controller.authModel.id}
networkHandlers={controller.networkHandlers}
formId={controller.template?.id}
config={dialog.config}
authModelId={dialog.authModelId}
networkHandlers={dialog.networkHandlers}
formId={dialog.template?.id}
allowSaveCredentials={credentialsSavingEnabled}
disabled={controller.isConnecting}
disabled={dialog.processing}
hideFeatures={['nonSecuredProperty']}
className={s(styles, { connectionAuthenticationFormLoader: true })}
/>
</Form>
))}
</CommonDialogBody>
{controller.step === ConnectionStep.Connection && (
{dialog.step === ConnectionStep.Connection && (
<CommonDialogFooter>
{controller.responseMessage && (
{dialog.connectException && (
<ErrorMessage
text={controller.responseMessage}
text={errorDetails.message ?? translate('core_blocks_exception_message_error_message')}
hasDetails={errorDetails.hasDetails}
className={s(styles, { errorMessage: true })}
hasDetails={controller.hasDetails}
onShowDetails={controller.onShowDetails}
onShowDetails={errorDetails.open}
/>
)}
<ConnectionDialogFooter
isConnecting={controller.isConnecting}
onBack={() => controller.onStep(ConnectionStep.ConnectionTemplateSelect)}
onConnect={controller.onConnect}
isConnecting={dialog.processing}
onBack={() => dialog.setStep(ConnectionStep.ConnectionTemplateSelect)}
onConnect={dialog.connect}
/>
</CommonDialogFooter>
)}
@@ -0,0 +1,11 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 enum ConnectionStep {
ConnectionTemplateSelect,
Connection,
}
@@ -8,24 +8,29 @@
import { observer } from 'mobx-react-lite';
import { useMemo, useState } from 'react';
import { ItemList, ItemListSearch } from '@cloudbeaver/core-blocks';
import type { Connection, DBDriver } from '@cloudbeaver/core-connections';
import { ItemList, ItemListSearch, useResource } from '@cloudbeaver/core-blocks';
import { DBDriverResource } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { TemplateConnectionsResource } from '../../TemplateConnectionsResource';
import { TemplateConnectionsService } from '../../TemplateConnectionsService';
import { TemplateConnectionItem } from './TemplateConnectionItem';
interface Props {
templateConnections: Connection[];
dbDrivers: Map<string, DBDriver>;
className?: string;
onSelect: (dbSourceId: string) => void;
}
export const TemplateConnectionSelector = observer<Props>(function TemplateConnectionSelector({
templateConnections,
dbDrivers,
className,
onSelect,
}) {
export const TemplateConnectionSelector = observer<Props>(function TemplateConnectionSelector({ className, onSelect }) {
useResource(TemplateConnectionSelector, ProjectInfoResource, CachedMapAllKey, { forceSuspense: true });
useResource(TemplateConnectionSelector, TemplateConnectionsResource, undefined, { forceSuspense: true });
const dbDriverResource = useResource(TemplateConnectionSelector, DBDriverResource, CachedMapAllKey);
const templateConnectionsService = useService(TemplateConnectionsService);
const templateConnections = templateConnectionsService.projectTemplates;
const [search, setSearch] = useState('');
const filteredTemplateConnections = useMemo(() => {
if (!search) {
@@ -39,7 +44,12 @@ export const TemplateConnectionSelector = observer<Props>(function TemplateConne
<ItemListSearch onChange={setSearch} />
<ItemList className={className}>
{filteredTemplateConnections.map(template => (
<TemplateConnectionItem key={template.id} template={template} dbDriver={dbDrivers.get(template.driverId)} onSelect={onSelect} />
<TemplateConnectionItem
key={template.id}
template={template}
dbDriver={dbDriverResource.resource.get(template.driverId)}
onSelect={onSelect}
/>
))}
</ItemList>
</>
@@ -0,0 +1,208 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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, computed, observable } from 'mobx';
import { AuthProviderService } from '@cloudbeaver/core-authentication';
import { useObservableRef } from '@cloudbeaver/core-blocks';
import {
Connection,
ConnectionInfoProjectKey,
ConnectionInfoResource,
ConnectionInitConfig,
createConnectionParam,
DBDriver,
DBDriverResource,
USER_NAME_PROPERTY_ID,
} from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { ProjectsService } from '@cloudbeaver/core-projects';
import { NetworkHandlerAuthType } from '@cloudbeaver/core-sdk';
import { getUniqueName } from '@cloudbeaver/core-utils';
import type { IConnectionAuthenticationConfig } from '@cloudbeaver/plugin-connections';
import { TemplateConnectionsService } from '../TemplateConnectionsService';
import { ConnectionStep } from './EConnectionStep';
interface IState {
readonly networkHandlers: string[];
readonly authModelId: string | null;
step: ConnectionStep;
driver: DBDriver | null;
template: Connection | null;
config: IConnectionAuthenticationConfig;
processing: boolean;
connectException: Error | null;
connect: () => Promise<void>;
setStep: (step: ConnectionStep) => void;
getConfig: (projectId: string, connectionId: string) => ConnectionInitConfig;
selectTemplate: (templateId: string) => Promise<void>;
}
function getDefaultConfig(): IConnectionAuthenticationConfig {
return {
credentials: {},
networkHandlersConfig: [],
saveCredentials: false,
};
}
export function useConnectionDialog(onConnect?: () => void) {
const notificationService = useService(NotificationService);
const connectionInfoResource = useService(ConnectionInfoResource);
const templateConnectionsService = useService(TemplateConnectionsService);
const projectsService = useService(ProjectsService);
const authProviderService = useService(AuthProviderService);
const dbDriverResource = useService(DBDriverResource);
const state: IState = useObservableRef(
() => ({
get networkHandlers() {
return this.template?.networkHandlersConfig?.filter(handler => handler.enabled && !handler.savePassword).map(handler => handler.id) || [];
},
get authModelId() {
if (this.template?.authNeeded === false || this.driver?.anonymousAccess === true) {
return null;
}
return this.template?.authModel || this.driver?.defaultAuthModel || null;
},
step: ConnectionStep.ConnectionTemplateSelect,
template: null as Connection | null,
driver: null as DBDriver | null,
processing: false,
connectException: null,
config: getDefaultConfig(),
async connect() {
if (this.processing || !this.template || !this.projectsService.userProject) {
return;
}
try {
this.connectException = null;
this.processing = true;
const connections = await this.connectionInfoResource.load(ConnectionInfoProjectKey(this.projectsService.userProject.id));
const connectionNames = connections.map(connection => connection.name);
const uniqueConnectionName = getUniqueName(this.template.name || 'Template connection', connectionNames);
const connection = await this.connectionInfoResource.createFromTemplate(this.template.projectId, this.template.id, uniqueConnectionName);
if (connection.requiredAuth) {
const state = await this.authProviderService.requireProvider(connection.requiredAuth);
if (!state) {
this.setStep(ConnectionStep.ConnectionTemplateSelect);
await this.connectionInfoResource.deleteConnection(createConnectionParam(connection));
return;
}
}
try {
await this.connectionInfoResource.init(this.getConfig(connection.projectId, connection.id));
this.notificationService.logSuccess({ title: 'plugin_connection_template_connect_success', message: connection.name });
this.onConnect?.();
} catch (exception: any) {
this.connectException = exception;
await this.connectionInfoResource.deleteConnection(createConnectionParam(connection));
}
} catch (exception: any) {
this.connectException = exception;
} finally {
this.processing = false;
}
},
getConfig(projectId: string, connectionId: string) {
const config: ConnectionInitConfig = {
projectId,
connectionId,
};
if (this.authModelId) {
if (Object.keys(this.config.credentials).length > 0) {
config.credentials = this.config.credentials;
}
config.saveCredentials = this.config.saveCredentials;
}
if (this.config.networkHandlersConfig.length > 0) {
config.networkCredentials = this.config.networkHandlersConfig;
}
return config;
},
async selectTemplate(templateId: string) {
this.template = this.templateConnectionsService.projectTemplates.find(template => template.id === templateId)!;
this.driver = await this.dbDriverResource.load(this.template.driverId);
this.config = getDefaultConfig();
if (this.template.authNeeded) {
const property = this.template.authProperties?.find(property => property.id === USER_NAME_PROPERTY_ID);
if (property?.value) {
this.config.credentials[USER_NAME_PROPERTY_ID] = property.value;
}
}
for (const id of this.networkHandlers) {
const handler = this.template.networkHandlersConfig?.find(handler => handler.id === id);
if (handler && (handler.userName || handler.authType !== NetworkHandlerAuthType.Password)) {
this.config.networkHandlersConfig.push({
id: handler.id,
authType: handler.authType,
userName: handler.userName,
password: handler.password,
savePassword: handler.savePassword,
});
}
}
this.setStep(ConnectionStep.Connection);
if (!this.authModelId) {
this.connect();
}
},
setStep(step: ConnectionStep) {
this.step = step;
this.connectException = null;
if (step === ConnectionStep.ConnectionTemplateSelect) {
this.template = null;
this.driver = null;
}
},
}),
{
step: observable.ref,
config: observable,
template: observable.ref,
driver: observable.ref,
processing: observable.ref,
connectException: observable.ref,
networkHandlers: computed,
authModelId: computed,
connect: action.bound,
selectTemplate: action.bound,
setStep: action.bound,
},
{
templateConnectionsService,
projectsService,
notificationService,
authProviderService,
connectionInfoResource,
dbDriverResource,
onConnect,
},
);
return state;
}
@@ -31,10 +31,28 @@ export class TemplateConnectionsResource extends CachedDataResource<Connection[]
this.markOutdated();
}
});
connectionInfoResource.onItemDelete.addHandler(list => {
const isAnyTemplate = connectionInfoResource.get(ResourceKeyUtils.toList(list)).some(connection => connection?.template);
if (isAnyTemplate) {
connectionInfoResource.onDataOutdated.addHandler(key => {
const keyData = connectionInfoResource.get(key);
const connections = Array.isArray(keyData) ? keyData : [keyData];
if (connections.some(connection => connection?.template)) {
this.markOutdated();
}
});
connectionInfoResource.onItemUpdate.addHandler(list => {
const includesTemplate = connectionInfoResource.get(ResourceKeyUtils.toList(list)).some(connection => connection?.template);
if (includesTemplate) {
this.markOutdated();
}
});
connectionInfoResource.onItemDelete.addHandler(list => {
const includesTemplate = connectionInfoResource.get(ResourceKeyUtils.toList(list)).some(connection => connection?.template);
if (includesTemplate) {
this.markOutdated();
}
});
@@ -46,7 +64,7 @@ export class TemplateConnectionsResource extends CachedDataResource<Connection[]
customIncludeOriginDetails: false,
includeAuthProperties: true,
includeOrigin: false,
includeAuthNeeded: false,
includeAuthNeeded: true,
includeCredentialsSaved: false,
includeProperties: false,
includeProviderProperties: false,
@@ -8,5 +8,6 @@ export default [
['basicConnection_connectionDialog_passwordPlaceholder', 'password'],
['basicConnection_connectionDialog_connecting', 'Connecting...'],
['basicConnection_connectionDialog_connecting_message', 'Connecting to database...'],
['plugin_connection_template_connect_success', 'Connection is established'],
['plugin_connection_template_action_connection_template_label', 'From a Template'],
];
@@ -8,5 +8,6 @@ export default [
['basicConnection_connectionDialog_passwordPlaceholder', 'password'],
['basicConnection_connectionDialog_connecting', 'Collegamento...'],
['basicConnection_connectionDialog_connecting_message', 'Collegamento al database...'],
['plugin_connection_template_connect_success', 'Connection is established'],
['plugin_connection_template_action_connection_template_label', 'Dal Template'],
];
@@ -8,5 +8,6 @@ export default [
['basicConnection_connectionDialog_passwordPlaceholder', 'password'],
['basicConnection_connectionDialog_connecting', 'Подключение...'],
['basicConnection_connectionDialog_connecting_message', 'Подключение к базе...'],
['plugin_connection_template_connect_success', 'Подключение установлено'],
['plugin_connection_template_action_connection_template_label', 'Из шаблона'],
];
@@ -8,5 +8,6 @@ export default [
['basicConnection_connectionDialog_passwordPlaceholder', '密码'],
['basicConnection_connectionDialog_connecting', '连接中...'],
['basicConnection_connectionDialog_connecting_message', '连接数据库...'],
['plugin_connection_template_connect_success', 'Connection is established'],
['plugin_connection_template_action_connection_template_label', '从模板创建'],
];
@@ -33,15 +33,15 @@
"@cloudbeaver/core-ui": "~0.1.0",
"@cloudbeaver/core-utils": "~0.1.0",
"@cloudbeaver/plugin-connections": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -11,7 +11,7 @@ import styled, { css } from 'reshadow';
import { Icon, IconButton, Loader, StaticImage, useResource, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { DBDriverResource } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { BASE_TAB_STYLES, TabList, TabPanelList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui';
import { BASE_TAB_STYLES, TabPanelList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui';
import { ConnectionFormLoader } from '@cloudbeaver/plugin-connections';
import { CreateConnectionService } from '../CreateConnectionService';
@@ -7,17 +7,17 @@
*/
import { action, computed, observable } from 'mobx';
import { IAutoLoadable, useObservableRef } from '@cloudbeaver/core-blocks';
import { useObservableRef } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import type { DatabaseConnectionFragment } from '@cloudbeaver/core-sdk';
import { useTabState } from '@cloudbeaver/core-ui';
import { isArraysEqual, isContainsException } from '@cloudbeaver/core-utils';
import { ILoadableState, isArraysEqual, isContainsException } from '@cloudbeaver/core-utils';
import type { IConnectionAccessTabState } from './IConnectionAccessTabState';
interface State extends IAutoLoadable {
interface State extends ILoadableState {
state: IConnectionAccessTabState;
changed: boolean;
edit: () => void;
@@ -40,15 +40,15 @@
"@cloudbeaver/plugin-navigation-tree": "~0.1.0",
"@cloudbeaver/plugin-projects": "~0.1.0",
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
+3 -3
View File
@@ -23,9 +23,9 @@
"d3-drag": "^3.0.0"
},
"devDependencies": {
"@types/d3": "^7.4.0",
"@types/d3-drag": "^3.0.3",
"typescript": "^5.2.2"
"@types/d3": "^7.4.3",
"@types/d3-drag": "^3.0.7",
"typescript": "^5.3.2"
},
"peerDependencies": {}
}
@@ -34,7 +34,7 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
"@cloudbeaver/plugin-sql-editor": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1",
@@ -67,10 +67,10 @@
"@cloudbeaver/plugin-navigation-tree": "~0.1.0",
"@cloudbeaver/plugin-object-viewer": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -34,7 +34,7 @@
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
"@cloudbeaver/plugin-react-data-grid": "~0.1.0",
"@popperjs/core": "^2.11.8",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -68,10 +68,10 @@
"@cloudbeaver/plugin-navigation-tree": "~0.1.0",
"@cloudbeaver/plugin-object-viewer": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -29,15 +29,15 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-data-viewer": "~0.1.0",
"@cloudbeaver/plugin-sql-editor": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -40,7 +40,7 @@
"@cloudbeaver/plugin-codemirror6": "~0.1.0",
"@cloudbeaver/plugin-navigation-tabs": "~0.1.0",
"@cloudbeaver/plugin-object-viewer": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0",
"reshadow": "^0.0.1",
@@ -72,10 +72,10 @@
"@cloudbeaver/plugin-navigation-tree": "~0.1.0",
"@cloudbeaver/plugin-object-viewer": "~0.1.0",
"@cloudbeaver/tests-runner": "~0.1.0",
"@testing-library/jest-dom": "^6.1.3",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@testing-library/jest-dom": "^6.1.5",
"@types/jest": "^29.5.10",
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -14,6 +14,7 @@ import { ServerErrorType, ServerInternalError } from '@cloudbeaver/core-sdk';
import { errorOf } from '@cloudbeaver/core-utils';
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
import { useEffect } from 'react';
const style = css`
error {
@@ -110,11 +111,6 @@ export const TableError = observer<Props>(function TableError({ model, loading,
false,
);
if (errorInfo.error !== model.source.error) {
errorInfo.error = model.source.error || null;
errorInfo.display = !!model.source.error;
}
const internalServerError = errorOf(model.source.error, ServerInternalError);
const error = useErrorDetails(model.source.error);
const animated = useStateDelay(!!errorInfo.error && !loading, 1);
@@ -139,6 +135,15 @@ export const TableError = observer<Props>(function TableError({ model, loading,
};
}
// keep it like this or remove error another way:
// console goes with error that we cannot modify ref value in render method.
useEffect(() => {
if (errorInfo.error !== model.source.error) {
errorInfo.error = model.source.error || null;
errorInfo.display = !!model.source.error;
}
}, [errorInfo, model.source.error]);
return styled(style)(
<error {...use({ animated, collapsed: !errorInfo.display, errorHidden })} className={className}>
<error-body>
@@ -31,14 +31,14 @@
"@cloudbeaver/core-view": "~0.1.0",
"@cloudbeaver/plugin-navigation-tabs": "~0.1.0",
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
"mobx": "^6.10.2",
"mobx": "^6.12.0",
"mobx-react-lite": "^4.0.5",
"react": "^18.2.0"
},
"peerDependencies": {},
"devDependencies": {
"@types/react": "^18.2.25",
"typescript": "^5.2.2",
"typescript-plugin-css-modules": "^5.0.1"
"@types/react": "^18.2.42",
"typescript": "^5.3.2",
"typescript-plugin-css-modules": "^5.0.2"
}
}
@@ -36,9 +36,6 @@
{
"path": "../core-resource/tsconfig.json"
},
{
"path": "../core-theming/tsconfig.json"
},
{
"path": "../core-ui/tsconfig.json"
},

Some files were not shown because too many files have changed in this diff Show More