feat: CB-639 public connection edit form

This commit is contained in:
Wroud
2021-02-26 02:01:00 +03:00
parent 1043186f87
commit 2706d3ca4a
47 changed files with 602 additions and 283 deletions
+1
View File
@@ -27,6 +27,7 @@
"@cloudbeaver/core-sdk": "^0.1.0",
"@cloudbeaver/core-settings": "^0.1.0",
"@cloudbeaver/core-theming": "^0.1.0",
"@cloudbeaver/core-ui": "^0.1.0",
"@cloudbeaver/core-utils": "^0.1.0",
"@cloudbeaver/core-view": "^0.1.0",
"@cloudbeaver/core-executor": "^0.1.0",
@@ -42,7 +42,7 @@ const mainStyles = composes(
`
);
export function Main() {
export const Main: React.FC = function Main() {
return styled(useStyles(mainStyles, splitStyles))(
<space as="main">
<Split sticky={30}>
@@ -56,4 +56,4 @@ export function Main() {
</Split>
</space>
);
}
};
@@ -10,10 +10,11 @@ import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import {
Pane, ResizerControls, Split, splitHorizontalStyles, splitStyles
Pane, ResizerControls, SlideBox, SlideElement, slideBoxStyles, Split, splitHorizontalStyles, splitStyles
} from '@cloudbeaver/core-blocks';
import { useController } from '@cloudbeaver/core-di';
import { useController, useService } from '@cloudbeaver/core-di';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
import { NavigationTabsBar } from '../shared/NavigationTabs/NavigationTabsBar';
import { LogViewTab } from '../shared/ToolsPanel/LogViewTab/LogViewTab';
@@ -34,21 +35,36 @@ const styles = composes(
Pane:last-child {
flex: 0 0 30%;
}
SlideBox {
flex: 1;
}
SlideElement {
display: flex;
}
`
);
export const RightArea = observer(function RightArea() {
const controller = useController(LogViewTabController);
const optionsPanelService = useService(OptionsPanelService);
const OptionsPanel = optionsPanelService.getPanelComponent();
return styled(useStyles(styles, splitStyles, splitHorizontalStyles))(
<Split sticky={30} split="horizontal" mode={controller.isActive ? undefined : 'minimize'} keepRatio>
<Pane>
<NavigationTabsBar />
</Pane>
{controller.isActive && <ResizerControls />}
<Pane main>
<LogViewTab />
</Pane>
</Split>
return styled(useStyles(styles, splitStyles, splitHorizontalStyles, slideBoxStyles))(
<SlideBox open={optionsPanelService.active}>
<SlideElement>
<OptionsPanel />
</SlideElement>
<SlideElement>
<Split sticky={30} split="horizontal" mode={controller.isActive ? undefined : 'minimize'} keepRatio>
<Pane>
<NavigationTabsBar />
</Pane>
{controller.isActive && <ResizerControls />}
<Pane main>
<LogViewTab />
</Pane>
</Split>
</SlideElement>
</SlideBox>
);
});
@@ -70,7 +70,12 @@ export const Loader: React.FC<Props> = observer(function Loader({
if ('loading' in element) {
loading = element.loading;
} else if ('isLoading' in element) {
loading = element.isLoading() && (overlay || !element.isLoaded());
const loaded = element.isLoaded();
loading = element.isLoading() || !loaded;
if (!loaded) {
overlay = false;
}
}
}
}
@@ -0,0 +1,20 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
interface Props {
open?: boolean;
className?: string;
}
export const SlideBox: React.FC<Props> = function SlideBox({ children, className }) {
return (
<div className={className}>
{children}
</div>
);
};
@@ -0,0 +1,29 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { css } from 'reshadow';
export const slideBoxStyles = css`
SlideBox {
overflow: hidden;
white-space: nowrap;
& SlideElement {
width: 100%;
height: 100%;
display: inline-block;
vertical-align:top;
transition: transform cubic-bezier(0.4, 0.0, 0.2, 1) 0.6s;
transform: translateX(-100%);
}
&[open] SlideElement {
transform: translateX(0%);
}
}
`;
@@ -0,0 +1,19 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
interface Props {
className?: string;
}
export const SlideElement: React.FC<Props> = function SlideElement({ children, className }) {
return (
<div className={className}>
{children}
</div>
);
};
@@ -13,7 +13,7 @@ import { Translate } from '@cloudbeaver/core-localization';
import { DynamicStyle, useStyles } from '@cloudbeaver/core-theming';
import { TabContext } from '../TabContext';
import type { ITabData } from '../TabsContext';
import type { ITabData } from '../TabsContainer/ITabsContainer';
import { Tab } from './Tab';
import { TabIcon } from './TabIcon';
import type { TabProps } from './TabProps';
@@ -10,7 +10,7 @@ import type { PropsWithChildren } from 'react';
import type { DynamicStyle } from '@cloudbeaver/core-theming';
import type { ITabData } from '../TabsContext';
import type { ITabData } from '../TabsContainer/ITabsContainer';
export type TabProps = PropsWithChildren<{
tabId: string;
@@ -6,12 +6,13 @@
* you may not use this file except in compliance with the License.
*/
import { useContext, useEffect, useRef } from 'react';
import { useContext, useEffect } from 'react';
import type { IExecutorHandler } from '@cloudbeaver/core-executor';
import { useObjectRef } from '../../useObjectRef';
import { ITabData, TabsContext } from '../TabsContext';
import type { ITabData } from '../TabsContainer/ITabsContainer';
import { TabsContext } from '../TabsContext';
export function useTab(
tabId: string,
@@ -12,7 +12,7 @@ import styled from 'reshadow';
import { DynamicStyle, useStyles } from '@cloudbeaver/core-theming';
import { TabPanel } from './TabPanel';
import type { ITabInfo } from './TabsContainer';
import type { ITabInfo } from './TabsContainer/ITabsContainer';
import { TabsContext } from './TabsContext';
interface Props {
@@ -29,7 +29,7 @@ export const TabsBox = forwardRef<HTMLDivElement, TabsBoxProps>(function TabsBox
className,
style = [],
}: TabsBoxProps, ref) {
return styled(useStyles(...style))(
return styled(useStyles(style))(
<TabsState currentTabId={currentTabId}>
<tabs-box ref={ref} as="div" className={className} tabIndex={tabIndex}>
{tabs && (
@@ -13,7 +13,7 @@ import { useStyles, Style } from '@cloudbeaver/core-theming';
import { Tab } from '../Tab';
import { TabPanel } from '../TabPanel';
import type { ITabData } from '../TabsContext';
import type { ITabData } from '../TabsContainer/ITabsContainer';
import { TabsBox } from './TabsBox';
export interface TabElementProps {
@@ -6,12 +6,14 @@
* you may not use this file except in compliance with the License.
*/
import { computed, observable, makeObservable } from 'mobx';
import type { MetadataValueGetter } from '@cloudbeaver/core-utils';
import type { TabProps } from './Tab/TabProps';
import type { ITabData } from './TabsContext';
import type { TabProps } from '../Tab/TabProps';
export interface ITabData<T = Record<string, any>> {
tabId: string;
props: T;
}
export type TabContainerTabComponent<TProps = Record<string, any>> = React.FC<TabProps & TProps>;
export type TabContainerPanelComponent<TProps = Record<string, any>> = React.FC<{ tabId: string } & TProps>;
@@ -42,35 +44,8 @@ export interface ITabInfo<
order: number;
}
export class TabsContainer<TProps = Record<string, any>, TOptions extends Record<string, any> = never> {
readonly tabInfoMap: Map<string, ITabInfo<TProps, TOptions>>;
get tabInfoList(): Array<ITabInfo<TProps, TOptions>> {
return Array.from(this.tabInfoMap.values())
.sort((a, b) => a.order - b.order);
}
constructor() {
makeObservable(this, {
tabInfoMap: observable.shallow,
tabInfoList: computed,
});
this.tabInfoMap = new Map();
}
getDisplayed(props?: TProps): Array<ITabInfo<TProps, TOptions>> {
return this.tabInfoList.filter(tabInfo => !tabInfo.isHidden?.(tabInfo.key, props));
}
add(tabInfo: ITabInfoOptions<TProps, TOptions>): void {
if (this.tabInfoMap.has(tabInfo.key)) {
throw new Error('Tab with same key already exists');
}
this.tabInfoMap.set(tabInfo.key, {
...tabInfo,
order: tabInfo.order ?? Number.MAX_SAFE_INTEGER,
});
}
export interface ITabsContainer<TProps = Record<string, any>, TOptions extends Record<string, any> = never> {
tabInfoList: Array<ITabInfo<TProps, TOptions>>;
getTabInfo: (tabId: string) => ITabInfo<TProps, TOptions> | undefined;
getDisplayed: (props?: TProps) => Array<ITabInfo<TProps, TOptions>>;
}
@@ -0,0 +1,49 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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, observable, makeObservable } from 'mobx';
import type { ITabInfo, ITabInfoOptions, ITabsContainer } from './ITabsContainer';
export class TabsContainer<TProps = Record<string, any>, TOptions extends Record<string, any> = never>
implements ITabsContainer<TProps, TOptions> {
readonly tabInfoMap: Map<string, ITabInfo<TProps, TOptions>>;
get tabInfoList(): Array<ITabInfo<TProps, TOptions>> {
return Array.from(this.tabInfoMap.values())
.sort((a, b) => a.order - b.order);
}
constructor() {
makeObservable(this, {
tabInfoMap: observable.shallow,
tabInfoList: computed,
});
this.tabInfoMap = new Map();
}
getTabInfo(tabId: string): ITabInfo<TProps, TOptions> | undefined {
return this.tabInfoMap.get(tabId);
}
getDisplayed(props?: TProps): Array<ITabInfo<TProps, TOptions>> {
return this.tabInfoList.filter(tabInfo => !tabInfo.isHidden?.(tabInfo.key, props));
}
add(tabInfo: ITabInfoOptions<TProps, TOptions>): void {
if (this.tabInfoMap.has(tabInfo.key)) {
throw new Error('Tab with same key already exists');
}
this.tabInfoMap.set(tabInfo.key, {
...tabInfo,
order: tabInfo.order ?? Number.MAX_SAFE_INTEGER,
});
}
}
@@ -12,18 +12,13 @@ import type { TabStateReturn } from 'reakit/Tab';
import type { IExecutor } from '@cloudbeaver/core-executor';
import type { MetadataMap } from '@cloudbeaver/core-utils';
import type { ITabInfo, TabsContainer } from './TabsContainer';
export interface ITabData<T = Record<string, any>> {
tabId: string;
props: T;
}
import type { ITabData, ITabInfo, ITabsContainer } from './TabsContainer/ITabsContainer';
export interface ITabsContext<T = Record<string, any>> {
state: TabStateReturn;
tabsState: MetadataMap<string, any>;
props: T;
container?: TabsContainer<T>;
container?: ITabsContainer<T>;
openExecutor: IExecutor<ITabData<T>>;
closeExecutor: IExecutor<ITabData<T>>;
lazy: boolean;
@@ -13,14 +13,14 @@ import { Executor, ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/co
import { MetadataMap } from '@cloudbeaver/core-utils';
import { useObjectRef } from '../useObjectRef';
import type { TabsContainer } from './TabsContainer';
import { TabsContext, ITabsContext, ITabData } from './TabsContext';
import type { ITabData, ITabsContainer } from './TabsContainer/ITabsContainer';
import { TabsContext, ITabsContext } from './TabsContext';
type Props<T = Record<string, any>> = T & React.PropsWithChildren<{
selectedId?: string;
orientation?: 'horizontal' | 'vertical';
currentTabId?: string | null;
container?: TabsContainer<T, any>;
container?: ITabsContainer<T, any>;
localState?: MetadataMap<string, any>;
lazy?: boolean;
manual?: boolean;
@@ -120,7 +120,7 @@ export function TabsState<T = Record<string, any>>({
props: dynamic.props,
}), []);
const getTabInfo = useCallback((tabId: string) => dynamic.container?.tabInfoMap.get(tabId), []);
const getTabInfo = useCallback((tabId: string) => dynamic.container?.getTabInfo(tabId), []);
const value = useMemo<ITabsContext<T>>(() => ({
state,
@@ -4,7 +4,8 @@ export * from './TabList';
export * from './TabPanel';
export * from './TabPanelList';
export * from './TabPanelProps';
export * from './TabsContainer';
export * from './TabsContainer/ITabsContainer';
export * from './TabsContainer/TabsContainer';
export * from './TabsContext';
export * from './TabsState';
export * from './useTabState';
+3
View File
@@ -11,6 +11,9 @@ export * from './Placeholder/Placeholder';
export * from './Placeholder/PlaceholderContainer';
export * from './PropertiesTable';
export * from './SanitizedHTML/SanitizedHTML';
export * from './Slide/SlideBox';
export * from './Slide/SlideBoxStyles';
export * from './Slide/SlideElement';
export * from './Split';
export * from './Table';
export * from './Tabs';
@@ -33,6 +33,7 @@
"@cloudbeaver/core-sdk": "^0.1.0",
"@cloudbeaver/core-settings": "^0.1.0",
"@cloudbeaver/core-theming": "^0.1.0",
"@cloudbeaver/core-ui": "^0.1.0",
"@cloudbeaver/core-utils": "^0.1.0",
"@cloudbeaver/core-view": "^0.1.0",
"react": "^16.x.x",
@@ -92,6 +92,7 @@ import { RouterService, ScreenService } from '@cloudbeaver/core-routing';
import { EnvironmentService, GraphQLService } from '@cloudbeaver/core-sdk';
import { LocalStorageSaveService, SettingsService } from '@cloudbeaver/core-settings';
import { ThemeService } from '@cloudbeaver/core-theming';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
import { ActiveViewService } from '@cloudbeaver/core-view';
export const coreManifest: PluginManifest = {
@@ -154,6 +155,7 @@ export const coreManifest: PluginManifest = {
EnvironmentService,
ExceptionsCatcherService,
EventsSettingsService,
OptionsPanelService,
GraphQLService,
LocalStorageSaveService,
LocalizationService,
@@ -235,6 +235,9 @@ export class ConnectionsResource extends CachedMapResource<string, DatabaseConne
}
export function isLocalConnection(connection: DatabaseConnection): boolean {
if (!connection.origin) {
return true;
}
return connection.origin.type === 'local';
}
@@ -83,7 +83,10 @@ export class ConnectionOptionsTabService extends Bootstrap {
}
} else {
if (submitType === 'submit') {
if (options.mode === 'create') {
if (options.mode === 'edit') {
const connection = await this.connectionInfoResource.update(config);
status.info(`Connection ${connection.name} updated`);
} else {
const connection = await this.connectionInfoResource.createConnection(config);
config.connectionId = connection.id;
status.info(`Connection ${connection.name} created`);
@@ -180,6 +180,19 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
return this.get(id)!;
}
async update(config: ConnectionConfig): Promise<DatabaseConnection> {
await this.performUpdate(config.connectionId!, [], async () => {
const { connection } = await this.graphQLService.sdk.updateConnection({
config,
...this.getDefaultIncludes(),
...this.getIncludesMap(config.connectionId!),
});
this.updateConnection(connection);
});
return this.get(config.connectionId!)!;
}
async close(id: string): Promise<Connection> {
await this.performUpdate(id, [], async () => {
const { connection } = await this.graphQLService.sdk.closeConnection({
@@ -5,6 +5,7 @@ export * from './ConnectionForm/OriginInfo/ConnectionOriginInfoTabService';
export * from './ConnectionForm/ConnectionAccess/ConnectionAccessTabService';
export * from './ConnectionForm/ConnectionFormService';
export * from './ConnectionForm/useConnectionFormState';
export * from './ConnectionForm/ConnectionForm';
export * from './Administration/Connections/ConnectionsAdministration';
export * from './Administration/Connections/ConnectionsAdministrationNavService';
export * from './Administration/Connections/ConnectionsAdministrationService';
@@ -14,7 +14,7 @@ import type { IExecutorHandler } from './IExecutorHandler';
import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection';
import { BlockedExecution, TaskScheduler } from './TaskScheduler/TaskScheduler';
export class Executor<T = unknown> implements IExecutor<T> {
export class Executor<T = void> implements IExecutor<T> {
private collection: ExecutorHandlersCollection<T>;
private scheduler: TaskScheduler<T>;
@@ -10,7 +10,7 @@ import type { IExecutionContext, IExecutionContextProvider } from './IExecutionC
import type { IExecutorHandler } from './IExecutorHandler';
import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection';
export interface IExecutor<T = unknown> {
export interface IExecutor<T = void> {
before: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
next: <TNext extends T>(executor: IExecutor<TNext>, map?: (data: T) => TNext) => this;
execute: (
@@ -0,0 +1,12 @@
mutation updateConnection(
$config: ConnectionConfig!
$includeOrigin: Boolean!,
$customIncludeOriginDetails: Boolean!,
$includeAuthProperties: Boolean!,
$customIncludeNetworkHandlerCredentials: Boolean!
) {
connection: updateConnection(config: $config) {
...DatabaseConnection
}
}
+25
View File
@@ -279,6 +279,7 @@ export interface Mutation {
testConnection: ConnectionInfo;
testNetworkHandler: NetworkEndpointInfo;
touchSession?: Maybe<Scalars['Boolean']>;
updateConnection: ConnectionInfo;
updateResultsData?: Maybe<SqlExecuteInfo>;
updateResultsDataBatch?: Maybe<SqlExecuteInfo>;
}
@@ -389,6 +390,10 @@ export interface MutationTestNetworkHandlerArgs {
config: NetworkHandlerConfigInput;
}
export interface MutationUpdateConnectionArgs {
config: ConnectionConfig;
}
export interface MutationUpdateResultsDataArgs {
connectionId: Scalars['ID'];
contextId: Scalars['ID'];
@@ -1217,6 +1222,16 @@ export type TestNetworkHandlerMutationVariables = Exact<{
export interface TestNetworkHandlerMutation { testNetworkHandler: Pick<NetworkEndpointInfo, 'message' | 'clientVersion' | 'serverVersion'> }
export type UpdateConnectionMutationVariables = Exact<{
config: ConnectionConfig;
includeOrigin: Scalars['Boolean'];
customIncludeOriginDetails: Scalars['Boolean'];
includeAuthProperties: Scalars['Boolean'];
customIncludeNetworkHandlerCredentials: Scalars['Boolean'];
}>;
export interface UpdateConnectionMutation { connection: DatabaseConnectionFragment }
export type ExportDataFromContainerQueryVariables = Exact<{
connectionId: Scalars['ID'];
containerNodePath: Scalars['ID'];
@@ -2045,6 +2060,13 @@ export const TestNetworkHandlerDocument = `
}
}
`;
export const UpdateConnectionDocument = `
mutation updateConnection($config: ConnectionConfig!, $includeOrigin: Boolean!, $customIncludeOriginDetails: Boolean!, $includeAuthProperties: Boolean!, $customIncludeNetworkHandlerCredentials: Boolean!) {
connection: updateConnection(config: $config) {
...DatabaseConnection
}
}
${DatabaseConnectionFragmentDoc}`;
export const ExportDataFromContainerDocument = `
query exportDataFromContainer($connectionId: ID!, $containerNodePath: ID!, $parameters: DataTransferParameters!) {
taskInfo: dataTransferExportDataFromContainer(
@@ -2603,6 +2625,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
testNetworkHandler(variables: TestNetworkHandlerMutationVariables): Promise<TestNetworkHandlerMutation> {
return withWrapper(() => client.request<TestNetworkHandlerMutation>(TestNetworkHandlerDocument, variables));
},
updateConnection(variables: UpdateConnectionMutationVariables): Promise<UpdateConnectionMutation> {
return withWrapper(() => client.request<UpdateConnectionMutation>(UpdateConnectionDocument, variables));
},
exportDataFromContainer(variables: ExportDataFromContainerQueryVariables): Promise<ExportDataFromContainerQuery> {
return withWrapper(() => client.request<ExportDataFromContainerQuery>(ExportDataFromContainerDocument, variables));
},
+21
View File
@@ -0,0 +1,21 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/lib
# misc
.DS_Store
.env*
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@cloudbeaver/core-ui",
"sideEffects": false,
"version": "0.1.0",
"description": "",
"license": "Apache-2.0",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "src/index.ts",
"scripts": {
"prebuild": "rimraf lib/*",
"build": "webpack --config ../../configs/webpack.plugin.config.js",
"postbuild": "tsc",
"lint": "eslint ./src/ --ext .ts,.tsx",
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
},
"peerDependencies": {
"@cloudbeaver/core-di": "^0.1.0",
"@cloudbeaver/core-executor": "^0.1.0",
"mobx": "^5.x.x"
}
}
@@ -0,0 +1,64 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { makeObservable, observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor';
@injectable()
export class OptionsPanelService {
active: boolean;
closeTask: IExecutor;
panelComponent: (() => React.FC) | null;
constructor() {
makeObservable(this, {
active: observable,
panelComponent: observable,
});
this.active = false;
this.panelComponent = null;
this.closeTask = new Executor();
}
getPanelComponent(): React.FC {
if (this.panelComponent === null) {
return () => null;
}
return this.panelComponent();
}
async open(component: () => React.FC): Promise<void> {
if (!(await this.close(component))) {
return;
}
this.panelComponent = component;
this.active = true;
}
async close(component: () => React.FC): Promise<boolean> {
if (component !== this.panelComponent || this.panelComponent === null) {
return true;
}
const contexts = await this.closeTask.execute();
const interrupted = contexts.getContext(ExecutorInterrupter.interruptContext);
if (interrupted.interrupted) {
return false;
}
this.panelComponent = null;
this.active = false;
return true;
}
}
+1
View File
@@ -0,0 +1 @@
export * from './Screens/AppScreen/OptionsPanelService';
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": ["src"],
"exclude": ["**/node_modules", "**/.*/"],
}
@@ -1,115 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { useAdministrationSettings } from '@cloudbeaver/core-administration';
import {
InputField,
useFocus,
ObjectPropertyInfoForm,
InputGroup,
FieldCheckbox,
SubmittingForm,
FormBox,
FormBoxElement,
FormGroup,
} from '@cloudbeaver/core-blocks';
import type { DBDriver } from '@cloudbeaver/core-connections';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IFormController } from './IFormController';
import { ParametersForm } from './ParametersForm';
interface ConnectionFormProps {
driver: DBDriver | null;
controller: IFormController;
}
const connectionFormStyles = css`
SubmittingForm {
flex: 1;
}
FormBoxElement {
flex: 1;
}
FormBox {
padding: 24px 4px 24px;
}
`;
export const ConnectionForm = observer(function ConnectionForm({
driver,
controller,
}: ConnectionFormProps) {
const translate = useTranslate();
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
const { credentialsSavingEnabled } = useAdministrationSettings();
return styled(useStyles(connectionFormStyles))(
<SubmittingForm ref={focusedRef} onChange={controller.onChange}>
<FormBox>
<FormBoxElement>
<FormGroup>
<InputField
type="text"
name="name"
disabled={controller.isConnecting}
mod='surface'
state={controller.config}
>
{translate('customConnection_custom_name')}
</InputField>
</FormGroup>
{controller.isUrlConnection ? (
<FormGroup>
<InputField
type="text"
name="url"
state={controller.config}
disabled={controller.isConnecting}
mod='surface'
>
{translate('customConnection_url_JDBC')}
</InputField>
</FormGroup>
) : (
<ParametersForm controller={controller} embedded={driver?.embedded} />
)}
{controller.authModel && (
<>
<FormGroup>
<InputGroup>{translate('connections_connection_edit_authentication')}</InputGroup>
</FormGroup>
<ObjectPropertyInfoForm
autofillToken='new-password'
properties={controller.authModel.properties}
state={controller.config.credentials}
disabled={controller.isConnecting}
/>
{credentialsSavingEnabled && (
<FormGroup>
<FieldCheckbox
name="saveCredentials"
value={controller.authModel.id}
state={controller.config}
checkboxLabel={translate('connections_connection_edit_save_credentials')}
disabled={controller.isConnecting}
mod='surface'
/>
</FormGroup>
)}
</>
)}
</FormBoxElement>
</FormBox>
</SubmittingForm>
);
});
@@ -1,17 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { ConnectionConfig, DatabaseAuthModel } from '@cloudbeaver/core-sdk';
export interface IFormController {
authModel?: DatabaseAuthModel;
config: ConnectionConfig;
isUrlConnection: boolean;
isConnecting: boolean;
onChange: (value?: unknown, name?: string) => void;
}
@@ -1,78 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
import { InputField } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IFormController } from './IFormController';
interface ParametersFormProps {
controller: IFormController;
embedded?: boolean;
}
const parametersFormStyles = css`
sub-label {
composes: theme-typography--caption from global;
line-height: 14px;
}`;
export const ParametersForm = observer(function ParametersForm({
controller,
embedded,
}: ParametersFormProps) {
const translate = useTranslate();
return styled(useStyles(parametersFormStyles))(
<>
{!embedded && (
<layout-grid-inner as="div">
<layout-grid-cell as='div' {...use({ 'span-tablet': 12, 'span-desktop': 8 })}>
<InputField
type="text"
name="host"
disabled={controller.isConnecting}
mod='surface'
state={controller.config}
>
{translate('customConnection_custom_host')}
<sub-label as="div">{translate('customConnection_custom_obligatory')}</sub-label>
</InputField>
</layout-grid-cell>
<layout-grid-cell as='div' {...use({ 'span-tablet': 12, 'span-desktop': 4 })}>
<InputField
type="number"
name="port"
disabled={controller.isConnecting}
mod='surface'
state={controller.config}
short
>
{translate('customConnection_custom_port')}
</InputField>
</layout-grid-cell>
</layout-grid-inner>
)}
<group as="div">
<InputField
type="text"
name="databaseName"
disabled={controller.isConnecting}
mod='surface'
state={controller.config}
>
{translate('customConnection_custom_database')}
</InputField>
</group>
</>
);
});
@@ -14,7 +14,7 @@ import { TemplateConnectionsResource } from './TemplateConnectionsResource';
export const connectionTemplate: PluginManifest = {
info: {
name: 'Connection template plugin',
name: 'Connections plugin',
},
providers: [
@@ -0,0 +1,17 @@
# dependencies
/node_modules
# testing
/coverage
# production
/lib
# misc
.DS_Store
.env*
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -0,0 +1,40 @@
{
"name": "@cloudbeaver/plugin-connections",
"sideEffects": false,
"version": "0.1.0",
"description": "",
"license": "Apache-2.0",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "src/index.ts",
"scripts": {
"prebuild": "rimraf lib/*",
"build": "webpack --config ../../configs/webpack.plugin.config.js",
"postbuild": "tsc",
"lint": "eslint ./src/ --ext .ts,.tsx",
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
},
"peerDependencies": {
"@cloudbeaver/core-app": "^0.1.0",
"@cloudbeaver/core-blocks": "^0.1.0",
"@cloudbeaver/core-connections": "^0.1.0",
"@cloudbeaver/core-di": "^0.1.0",
"@cloudbeaver/core-dialogs": "^0.1.0",
"@cloudbeaver/core-events": "^0.1.0",
"@cloudbeaver/core-extensions": "^0.1.0",
"@cloudbeaver/core-localization": "^0.1.0",
"@cloudbeaver/core-plugin": "^0.1.0",
"@cloudbeaver/core-product": "^0.1.0",
"@cloudbeaver/core-root": "^0.1.0",
"@cloudbeaver/core-sdk": "^0.1.0",
"@cloudbeaver/core-settings": "^0.1.0",
"@cloudbeaver/core-theming": "^0.1.0",
"@cloudbeaver/core-ui": "^0.1.0",
"@cloudbeaver/core-utils": "^0.1.0",
"react": "^16.x.x",
"rxjs": "^6.x.x",
"mobx": "^5.x.x",
"mobx-react-lite": "^3.x.x",
"reshadow": "^0.x.x"
}
}
@@ -0,0 +1,63 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useCallback, useState } from 'react';
import styled from 'reshadow';
import { css } from 'reshadow';
import { Loader, useMapResource } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, IConnectionFormData, IConnectionFormOptions, ConnectionForm } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { PublicConnectionFormService } from './PublicConnectionFormService';
const styles = css`
Loader {
height: 100%;
}
`;
export const PublicConnectionForm: React.FC = observer(function PublicConnectionForm() {
const service = useService(PublicConnectionFormService);
const connection = useMapResource(ConnectionInfoResource, {
key: service.connectionId,
includes: ['includeOrigin', 'customIncludeNetworkHandlerCredentials', 'includeAuthProperties', 'customIncludeNetworkHandlerCredentials'],
});
const [data] = useState<IConnectionFormData>(() => ({
config: observable({}),
get info() {
return connection.data;
},
resource: connection.resource,
partsState: new MetadataMap<string, any>(),
}));
const [options] = useState<IConnectionFormOptions>(() => ({
mode: 'edit',
type: 'public',
}));
const close = useCallback(() => service.close(), []);
return styled(styles)(
<Loader state={connection}>
{() => (
<ConnectionForm
data={data}
options={options}
onCancel={close}
onSave={close}
/>
)}
</Loader>
);
});
@@ -0,0 +1,48 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 {
NavigationTreeContextMenuService,
EObjectFeature,
NodeManagerUtils,
NavNode
} from '@cloudbeaver/core-app';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ContextMenuService } from '@cloudbeaver/core-dialogs';
import { PublicConnectionFormService } from './PublicConnectionFormService';
@injectable()
export class PublicConnectionFormBootstrap extends Bootstrap {
constructor(
private readonly contextMenuService: ContextMenuService,
private readonly publicConnectionFormService: PublicConnectionFormService
) {
super();
}
register(): void | Promise<void> {
this.contextMenuService.addMenuItem<NavNode>(this.contextMenuService.getRootMenuToken(), {
id: 'connection-edit',
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
title: 'Edit',
order: 2,
onClick: context => {
const node = context.data;
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id);
this.publicConnectionFormService.open(connectionId);
},
});
}
load(): void | Promise<void> {
}
}
@@ -0,0 +1,35 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
import { PublicConnectionForm } from './PublicConnectionForm';
const formGetter = () => PublicConnectionForm;
@injectable()
export class PublicConnectionFormService {
connectionId: string | null;
constructor(
private readonly optionsPanelService: OptionsPanelService
) {
this.connectionId = null;
}
async open(connectionId: string): Promise<void> {
this.connectionId = connectionId;
await this.optionsPanelService.open(formGetter);
}
async close(): Promise<void> {
this.connectionId = null;
await this.optionsPanelService.close(formGetter);
}
}
@@ -0,0 +1,3 @@
import { connectionPlugin } from './manifest';
export default connectionPlugin;
@@ -0,0 +1,23 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { PluginManifest } from '@cloudbeaver/core-di';
import { PublicConnectionFormBootstrap } from './PublicConnectionForm/PublicConnectionFormBootstrap';
import { PublicConnectionFormService } from './PublicConnectionForm/PublicConnectionFormService';
export const connectionPlugin: PluginManifest = {
info: {
name: 'Connection template plugin',
},
providers: [
PublicConnectionFormBootstrap,
PublicConnectionFormService,
],
};
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "lib"
},
"include": ["src"],
"exclude": ["**/node_modules", "**/.*/"],
}
@@ -5,6 +5,7 @@ import administration from '@cloudbeaver/plugin-administration';
import authentication from '@cloudbeaver/plugin-authentication';
import connectionCustom from '@cloudbeaver/plugin-connection-custom';
import connectionTemplate from '@cloudbeaver/plugin-connection-template';
import connectionPlugin from '@cloudbeaver/plugin-connections';
import dataExport from '@cloudbeaver/plugin-data-export';
import spreadsheetNew from '@cloudbeaver/plugin-data-spreadsheet-new';
import dataViewer from '@cloudbeaver/plugin-data-viewer';
@@ -27,6 +28,7 @@ const PLUGINS = [
objectViewer,
sqlEditor,
defaultProductManifest,
connectionPlugin,
];
bootstrap(PLUGINS);