mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'devel' into feat/connection-form-new-design
This commit is contained in:
@@ -75,6 +75,16 @@ type WebServiceConfig {
|
||||
bundleVersion: String!
|
||||
}
|
||||
|
||||
type ProductInfo {
|
||||
id: ID!
|
||||
version: String!
|
||||
name: String!
|
||||
description: String
|
||||
buildTime: String!
|
||||
releaseTime: String!
|
||||
licenseInfo: String
|
||||
}
|
||||
|
||||
type ServerConfig {
|
||||
name: String!
|
||||
version: String!
|
||||
@@ -100,6 +110,7 @@ type ServerConfig {
|
||||
supportedLanguages: [ ServerLanguage! ]!
|
||||
services: [ WebServiceConfig ]
|
||||
productConfiguration: Object!
|
||||
productInfo: ProductInfo!
|
||||
defaultNavigatorSettings: NavigatorSettings!
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import io.cloudbeaver.server.CBApplication;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Web server configuration
|
||||
*/
|
||||
public class WebProductInfo {
|
||||
|
||||
public WebProductInfo() {
|
||||
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getId() {
|
||||
return CommonUtils.notEmpty(Platform.getProduct().getId());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getVersion() {
|
||||
return GeneralUtils.getProductVersion().toString();
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getName() {
|
||||
return CommonUtils.notEmpty(Platform.getProduct().getName());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getDescription() {
|
||||
return CommonUtils.notEmpty(Platform.getProduct().getDescription());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getBuildTime() {
|
||||
Date buildTime = GeneralUtils.getProductBuildTime();
|
||||
if (buildTime == null) {
|
||||
buildTime = new Date();
|
||||
}
|
||||
return DateFormat.getDateInstance(DateFormat.LONG).format(buildTime);
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getReleaseTime() {
|
||||
return DateFormat.getDateInstance(DateFormat.LONG).format(GeneralUtils.getProductReleaseDate());
|
||||
}
|
||||
|
||||
@Property
|
||||
public String getLicenseInfo() {
|
||||
return CBApplication.getInstance().getInfoDetails();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -146,4 +146,9 @@ public class WebServerConfig {
|
||||
return application.getAppConfiguration().getDefaultNavigatorSettings();
|
||||
}
|
||||
|
||||
@Property
|
||||
public WebProductInfo getProductInfo() {
|
||||
return new WebProductInfo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -465,7 +465,7 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
|
||||
@Override
|
||||
public String getInfoDetails() {
|
||||
return serverName;
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,15 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Loader } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CoreSettingsService } from '../CoreSettingsService';
|
||||
import type { NavNode } from '../shared/NodesManager/EntityTypes';
|
||||
import { useChildren } from '../shared/useChildren';
|
||||
import { NavigationNodeElement } from './NavigationTreeNode/NavigationNodeElement';
|
||||
@@ -42,7 +39,7 @@ interface Props {
|
||||
emptyPlaceholder: React.FC;
|
||||
className?: string;
|
||||
onOpen?: (node: NavNode) => Promise<void> | void;
|
||||
onSelect?: (node: NavNode, multiple: boolean) => boolean;
|
||||
onSelect?: (node: NavNode, multiple: boolean) => void;
|
||||
isSelected?: (node: NavNode) => boolean;
|
||||
}
|
||||
|
||||
@@ -55,10 +52,8 @@ export const ElementsTree: React.FC<Props> = observer(function ElementsTree({
|
||||
onSelect,
|
||||
isSelected,
|
||||
}) {
|
||||
const config = useService(CoreSettingsService);
|
||||
const nodeChildren = useChildren(root);
|
||||
const Placeholder = emptyPlaceholder;
|
||||
const limit = useMemo(() => computed(() => config.settings.getValue('app.navigationTree.childrenLimit')), [config]);
|
||||
|
||||
const context = useMemo<ITreeContext>(
|
||||
() => ({ control, onOpen, onSelect, isSelected }),
|
||||
@@ -80,7 +75,7 @@ export const ElementsTree: React.FC<Props> = observer(function ElementsTree({
|
||||
return styled(styles)(
|
||||
<TreeContext.Provider value={context}>
|
||||
<tree as="div" className={className}>
|
||||
{nodeChildren.children.slice(0, limit.get()).map(id => (
|
||||
{nodeChildren.children.map(id => (
|
||||
<NavigationNodeElement key={id} nodeId={id} />
|
||||
))}
|
||||
<Loader loading={nodeChildren.isLoading()} overlay />
|
||||
|
||||
@@ -29,11 +29,11 @@ export class NavigationTreeContextMenuService {
|
||||
private connectionInfoResource: ConnectionInfoResource
|
||||
) { }
|
||||
|
||||
getMenuToken() {
|
||||
getMenuToken(): string {
|
||||
return NavigationTreeContextMenuService.menuToken;
|
||||
}
|
||||
|
||||
getNodeViewMenuItemToken() {
|
||||
getNodeViewMenuItemToken(): string {
|
||||
return NavigationTreeContextMenuService.nodeViewMenuItemToken;
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -6,16 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { TreeNodeContext, TreeNodeNested, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { CoreSettingsService } from '../../../CoreSettingsService';
|
||||
import { useChildren } from '../../../shared/useChildren';
|
||||
|
||||
interface Props {
|
||||
@@ -29,11 +26,9 @@ export const NavigationNodeNested: React.FC<Props> = observer(function Navigatio
|
||||
nodeId,
|
||||
component,
|
||||
}) {
|
||||
const config = useService(CoreSettingsService);
|
||||
const styles = useStyles(TREE_NODE_STYLES);
|
||||
const context = useContext(TreeNodeContext);
|
||||
const children = useChildren(nodeId);
|
||||
const limit = useMemo(() => computed(() => config.settings.getValue('app.navigationTree.childrenLimit')), [config]);
|
||||
|
||||
if (!children.children || !context?.expanded) {
|
||||
return null;
|
||||
@@ -43,7 +38,7 @@ export const NavigationNodeNested: React.FC<Props> = observer(function Navigatio
|
||||
|
||||
return styled(styles)(
|
||||
<TreeNodeNested>
|
||||
{children.children.slice(0, limit.get()).map(child => <NavigationNode key={child} nodeId={child} />)}
|
||||
{children.children.map(child => <NavigationNode key={child} nodeId={child} />)}
|
||||
</TreeNodeNested>
|
||||
);
|
||||
});
|
||||
|
||||
+14
-7
@@ -27,18 +27,18 @@ interface INavigationNode {
|
||||
leaf: boolean;
|
||||
handleExpand: () => void;
|
||||
handleOpen: () => void;
|
||||
handleSelect: (isMultiple?: boolean) => boolean;
|
||||
handleSelect: (isMultiple?: boolean) => void;
|
||||
}
|
||||
|
||||
export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
const context = useContext(TreeContext);
|
||||
const navigationTreeService = useService(NavigationTreeService);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [isExpanded, switchExpand] = useState(false);
|
||||
const { isLoading, isOutdated } = useNode(node.id);
|
||||
const children = useChildren(node.id);
|
||||
const loading = isLoading() || children.isLoading() || processing;
|
||||
|
||||
const isExpanded = navigationTreeService.isNodeExpanded(node.id);
|
||||
let leaf = isLeaf(node) || (children.children?.length === 0 && !children.isOutdated());
|
||||
let expanded = isExpanded && !leaf;
|
||||
|
||||
@@ -60,11 +60,11 @@ export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
clearTimeout(timeout);
|
||||
setProcessing(false);
|
||||
if (!state) {
|
||||
switchExpand(false);
|
||||
navigationTreeService.expandNode(node.id, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
switchExpand(!expanded);
|
||||
navigationTreeService.expandNode(node.id, !expanded);
|
||||
};
|
||||
|
||||
const handleOpen = async () => {
|
||||
@@ -76,7 +76,9 @@ export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (multiple = false) => context?.onSelect?.(node, multiple) || false;
|
||||
const handleSelect = (multiple = false) => {
|
||||
context?.onSelect?.(node, multiple);
|
||||
};
|
||||
|
||||
// TODO: probably should be refactored
|
||||
useEffect(() => {
|
||||
@@ -87,7 +89,7 @@ export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
.then(state => {
|
||||
setProcessing(false);
|
||||
if (!state) {
|
||||
switchExpand(false);
|
||||
navigationTreeService.expandNode(node.id, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -97,7 +99,12 @@ export function useNavigationNode(node: NavNode): INavigationNode {
|
||||
if (node && context?.isSelected?.(node)) {
|
||||
context.onSelect?.(node, true);
|
||||
}
|
||||
}, [context, node]);
|
||||
}, [context, node.id]);
|
||||
|
||||
useEffect(() => () => {
|
||||
// TODO: seems like selection & expand should be specific for separate tree definitions
|
||||
navigationTreeService.expandNode(node.id, false);
|
||||
}, [node.id]);
|
||||
|
||||
return {
|
||||
control: context?.control,
|
||||
|
||||
@@ -6,43 +6,91 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { Subject, Observable } from 'rxjs';
|
||||
import { action, computed, makeObservable } from 'mobx';
|
||||
|
||||
import { ConnectionAuthService } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ResourceKeyUtils } from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
import type { IActiveView } from '@cloudbeaver/core-view';
|
||||
|
||||
import { CoreSettingsService } from '../CoreSettingsService';
|
||||
import { EObjectFeature } from '../shared/NodesManager/EObjectFeature';
|
||||
import { NavNodeExtensionsService } from '../shared/NodesManager/NavNodeExtensionsService';
|
||||
import { ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
|
||||
import { NavNodeInfoResource, ROOT_NODE_PATH } from '../shared/NodesManager/NavNodeInfoResource';
|
||||
import { NavNodeManagerService } from '../shared/NodesManager/NavNodeManagerService';
|
||||
import { NavTreeResource } from '../shared/NodesManager/NavTreeResource';
|
||||
import { NodeManagerUtils } from '../shared/NodesManager/NodeManagerUtils';
|
||||
|
||||
export interface INavigationNodeMetadata {
|
||||
selected: boolean;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export interface INavigationNodeSelectionData {
|
||||
id: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class NavigationTreeService {
|
||||
readonly selectedNodes = observable.array<string>([]);
|
||||
readonly onNodeSelect: Observable<[string, boolean]>;
|
||||
readonly navigationTreeMetadata: MetadataMap<string, INavigationNodeMetadata>;
|
||||
readonly nodeSelectionTask: IExecutor<INavigationNodeSelectionData>;
|
||||
|
||||
private nodeSelectSubject: Subject<[string, boolean]>;
|
||||
get childrenLimit(): number {
|
||||
return this.coreSettingsService.settings.getValue('app.navigationTree.childrenLimit');
|
||||
}
|
||||
|
||||
constructor(
|
||||
private navNodeManagerService: NavNodeManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionAuthService: ConnectionAuthService,
|
||||
private navNodeExtensionsService: NavNodeExtensionsService
|
||||
private navNodeExtensionsService: NavNodeExtensionsService,
|
||||
private navTreeResource: NavTreeResource,
|
||||
private coreSettingsService: CoreSettingsService,
|
||||
private navNodeInfoResource: NavNodeInfoResource
|
||||
) {
|
||||
this.nodeSelectSubject = new Subject();
|
||||
this.onNodeSelect = this.nodeSelectSubject.asObservable();
|
||||
makeObservable<NavigationTreeService, 'unselectAll'>(this, {
|
||||
childrenLimit: computed,
|
||||
unselectAll: action,
|
||||
});
|
||||
|
||||
this.nodeSelectionTask = new Executor();
|
||||
this.navigationTreeMetadata = new MetadataMap<string, INavigationNodeMetadata>(() => ({
|
||||
selected: false,
|
||||
expanded: false,
|
||||
}));
|
||||
this.getView = this.getView.bind(this);
|
||||
|
||||
this.navNodeInfoResource.onItemDelete.addHandler(key => {
|
||||
ResourceKeyUtils.forEach(key, key => {
|
||||
this.navigationTreeMetadata.delete(key);
|
||||
});
|
||||
});
|
||||
this.navTreeResource.onItemDelete.addHandler(key => {
|
||||
ResourceKeyUtils.forEach(key, key => {
|
||||
this.expandNode(key, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async navToNode(id: string, parentId: string) {
|
||||
getChildren(id: string): string[] | undefined {
|
||||
const children = this.navTreeResource.get(id);
|
||||
|
||||
if (children) {
|
||||
return children.slice(0, this.childrenLimit);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
async navToNode(id: string, parentId: string): Promise<void> {
|
||||
await this.navNodeManagerService.navToNode(id, parentId);
|
||||
}
|
||||
|
||||
async loadNestedNodes(id = ROOT_NODE_PATH) {
|
||||
async loadNestedNodes(id = ROOT_NODE_PATH): Promise<boolean> {
|
||||
try {
|
||||
if (this.isConnectionNode(id) && !await this.tryInitConnection(id)) {
|
||||
return false;
|
||||
@@ -55,39 +103,56 @@ export class NavigationTreeService {
|
||||
return false;
|
||||
}
|
||||
|
||||
selectNode(id: string, multiple?: boolean): boolean {
|
||||
async selectNode(id: string, multiple?: boolean): Promise<void> {
|
||||
if (!multiple) {
|
||||
for (const id of this.selectedNodes) {
|
||||
this.nodeSelectSubject.next([id, false]);
|
||||
}
|
||||
this.selectedNodes.clear();
|
||||
await this.unselectAll();
|
||||
}
|
||||
if (!this.isNodeSelected(id)) {
|
||||
this.selectedNodes.push(id);
|
||||
this.nodeSelectSubject.next([id, true]);
|
||||
return true;
|
||||
}
|
||||
this.selectedNodes.remove(id);
|
||||
this.nodeSelectSubject.next([id, false]);
|
||||
return false;
|
||||
|
||||
const metadata = this.navigationTreeMetadata.get(id);
|
||||
metadata.selected = !metadata.selected;
|
||||
|
||||
await this.nodeSelectionTask.execute({
|
||||
id,
|
||||
selected: metadata.selected,
|
||||
});
|
||||
}
|
||||
|
||||
isNodeSelected(navNodeId: string) {
|
||||
return this.selectedNodes.includes(navNodeId);
|
||||
isNodeExpanded(navNodeId: string): boolean {
|
||||
return this.navigationTreeMetadata.get(navNodeId).expanded;
|
||||
}
|
||||
|
||||
isNodeSelected(navNodeId: string): boolean {
|
||||
return this.navigationTreeMetadata.get(navNodeId).selected;
|
||||
}
|
||||
|
||||
expandNode(navNodeId: string, state: boolean): void {
|
||||
const metadata = this.navigationTreeMetadata.get(navNodeId);
|
||||
metadata.expanded = state;
|
||||
}
|
||||
|
||||
getView(): IActiveView<string> | null {
|
||||
if (!this.selectedNodes.length) {
|
||||
const element = Array.from(this.navigationTreeMetadata).find(([key, metadata]) => metadata.selected);
|
||||
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
const context = this.selectedNodes[0];
|
||||
|
||||
return {
|
||||
context,
|
||||
context: element[0],
|
||||
extensions: this.navNodeExtensionsService.extensions,
|
||||
};
|
||||
}
|
||||
|
||||
private async unselectAll() {
|
||||
for (const [id, metadata] of this.navigationTreeMetadata) {
|
||||
metadata.selected = false;
|
||||
await this.nodeSelectionTask.execute({
|
||||
id,
|
||||
selected: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private isConnectionNode(navNodeId: string) {
|
||||
const node = this.navNodeManagerService.getNode(navNodeId);
|
||||
return node?.objectFeatures.includes(EObjectFeature.dataSource);
|
||||
|
||||
@@ -15,7 +15,7 @@ export interface ITreeContext {
|
||||
node: NavNode;
|
||||
}>;
|
||||
onOpen?: (node: NavNode) => Promise<void> | void;
|
||||
onSelect?: (node: NavNode, multiple: boolean) => boolean;
|
||||
onSelect?: (node: NavNode, multiple: boolean) => void;
|
||||
isSelected?: (node: NavNode) => boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { NavigationTreeService } from './NavigationTreeService';
|
||||
interface INavigationTree {
|
||||
isSelected: (node: NavNode) => boolean;
|
||||
handleOpen: (node: NavNode) => Promise<void>;
|
||||
handleSelect: (node: NavNode, isMultiple: boolean) => boolean;
|
||||
handleSelect: (node: NavNode, isMultiple: boolean) => void;
|
||||
}
|
||||
|
||||
export function useNavigationTree(): INavigationTree {
|
||||
|
||||
@@ -13,9 +13,9 @@ import {
|
||||
import { useImperativeHandle } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Icon } from '@cloudbeaver/core-blocks';
|
||||
import { Icon, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
|
||||
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { EditorDialog } from './EditorDialog';
|
||||
@@ -64,33 +64,39 @@ export const InlineEditor = observer<InlineEditorProps, HTMLInputElement | null>
|
||||
onReject,
|
||||
className,
|
||||
}, ref) {
|
||||
const props = useObjectRef({
|
||||
onChange,
|
||||
onReject,
|
||||
onSave,
|
||||
value,
|
||||
});
|
||||
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
|
||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>| string) => {
|
||||
const newValue = typeof event === 'string' ? event : event.target.value;
|
||||
onChange(newValue);
|
||||
}, [onChange]);
|
||||
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
props.onChange(event.target.value);
|
||||
}, []);
|
||||
|
||||
const handlePopup = useCallback(async () => {
|
||||
const newValue = await commonDialogService.open(EditorDialog, value);
|
||||
if (typeof newValue === 'string') {
|
||||
handleChange(newValue);
|
||||
onSave();
|
||||
} else if (onReject) {
|
||||
onReject();
|
||||
const newValue = await commonDialogService.open(EditorDialog, props.value);
|
||||
if (newValue === DialogueStateResult.Rejected || newValue === DialogueStateResult.Resolved) {
|
||||
props.onReject?.();
|
||||
} else {
|
||||
props.onChange(newValue);
|
||||
props.onSave();
|
||||
}
|
||||
}, [value, commonDialogService, onSave, onReject, handleChange]);
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
onSave();
|
||||
props.onSave();
|
||||
break;
|
||||
case 'Escape':
|
||||
onReject?.();
|
||||
props.onReject?.();
|
||||
break;
|
||||
}
|
||||
}, [onSave, onReject]);
|
||||
}, []);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
|
||||
@@ -332,20 +332,23 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
};
|
||||
|
||||
async updateRoot(): Promise<void> {
|
||||
if (await this.isNavTreeEnabled()) {
|
||||
const enabled = await this.isNavTreeEnabled();
|
||||
if (enabled) {
|
||||
await this.navTree.refresh(ROOT_NODE_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
async refreshRoot(): Promise<void> {
|
||||
this.navTree.delete(ROOT_NODE_PATH);
|
||||
if (await this.isNavTreeEnabled()) {
|
||||
const enabled = await this.isNavTreeEnabled();
|
||||
if (enabled) {
|
||||
await this.navTree.refresh(ROOT_NODE_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
private async connectionCreateHandler(connection: Connection) {
|
||||
if (!await this.isNavTreeEnabled()) {
|
||||
const enabled = await this.isNavTreeEnabled();
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -360,7 +363,8 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async connectionRefreshHandler(connections: Connection[]) {
|
||||
if (!await this.isNavTreeEnabled()) {
|
||||
const enabled = await this.isNavTreeEnabled();
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -368,7 +372,8 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async connectionUpdateHandler(key: ResourceKey<string>) {
|
||||
if (!await this.isNavTreeEnabled()) {
|
||||
const enabled = await this.isNavTreeEnabled();
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -430,7 +435,8 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async isNavTreeEnabled() {
|
||||
if (!await this.permissionsService.hasAsync(EPermission.public)) {
|
||||
const active = await this.permissionsService.hasAsync(EPermission.public);
|
||||
if (!active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useCallback } from 'react';
|
||||
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
|
||||
import { NavigationTreeService } from '../NavigationTree/NavigationTreeService';
|
||||
import { ROOT_NODE_PATH } from './NodesManager/NavNodeInfoResource';
|
||||
import { NavTreeResource } from './NodesManager/NavTreeResource';
|
||||
|
||||
@@ -21,8 +22,9 @@ interface Hook {
|
||||
}
|
||||
|
||||
export function useChildren(navNodeId = ROOT_NODE_PATH): Hook {
|
||||
const navTreeService = useService(NavigationTreeService);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
const children = navTreeResource.get(navNodeId);
|
||||
const children = navTreeService.getChildren(navNodeId);
|
||||
|
||||
const deps = [navNodeId];
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
*/
|
||||
|
||||
import styled, { css } from 'reshadow';
|
||||
import { use } from 'reshadow';
|
||||
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Loader } from './Loader/Loader';
|
||||
|
||||
const buttonStyles = composes(
|
||||
css`
|
||||
Button {
|
||||
@@ -22,7 +25,31 @@ const buttonStyles = composes(
|
||||
css`
|
||||
Button {
|
||||
display: flex;
|
||||
|
||||
& Loader, & button-label {
|
||||
transition: opacity cubic-bezier(0.4, 0.0, 0.2, 1) 0.3s;
|
||||
}
|
||||
|
||||
& Loader {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
& button-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&[|loading] {
|
||||
& Loader {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& button-label {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
);
|
||||
|
||||
@@ -61,6 +88,7 @@ type ButtonProps = (
|
||||
React.ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement>
|
||||
& React.LinkHTMLAttributes<HTMLLinkElement | HTMLButtonElement>
|
||||
) & {
|
||||
loading?: boolean;
|
||||
mod?: Array<keyof typeof buttonMod>;
|
||||
tag?: 'button' | 'a';
|
||||
href?: string;
|
||||
@@ -72,10 +100,20 @@ export const Button: React.FC<ButtonProps> = function Button({
|
||||
children,
|
||||
mod,
|
||||
tag = 'button',
|
||||
disabled = false,
|
||||
loading,
|
||||
...rest
|
||||
}) {
|
||||
if (loading) {
|
||||
disabled = true;
|
||||
}
|
||||
|
||||
const Button = tag;
|
||||
return styled(useStyles(buttonStyles, ...(mod || []).map(mod => buttonMod[mod])))(
|
||||
<Button {...rest}><ripple as="div" />{children}</Button>
|
||||
<Button {...rest} disabled={disabled} {...use({ loading })}>
|
||||
<ripple as="div" />
|
||||
<button-label as='div'>{children}</button-label>
|
||||
<Loader small />
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { use } from 'reshadow';
|
||||
|
||||
import type { IGridItemsLayoutProps } from '../../Containers/ILayoutContainerProps';
|
||||
import { FormContext } from '../FormContext';
|
||||
@@ -62,6 +62,7 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
onChange,
|
||||
...rest
|
||||
}: CheckboxControlledProps | CheckboxObjectProps<any, any>) {
|
||||
const [count, refresh] = useState(0);
|
||||
const context = useContext(FormContext);
|
||||
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (state) {
|
||||
@@ -84,7 +85,8 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
if (context) {
|
||||
context.onChange(event.target.checked, name);
|
||||
}
|
||||
}, [state, name, value, onChange, context]);
|
||||
refresh(count + 1);
|
||||
}, [state, name, value, onChange, context, count]);
|
||||
|
||||
if (autoHide && !isControlPresented(name, state)) {
|
||||
return null;
|
||||
@@ -104,7 +106,7 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
checked = checked.includes(value);
|
||||
}
|
||||
|
||||
return styled()(
|
||||
return (
|
||||
<CheckboxMarkup
|
||||
{...rest}
|
||||
name={name}
|
||||
|
||||
@@ -11,11 +11,13 @@ import { useCallback, forwardRef, useMemo } from 'react';
|
||||
import { FormContext } from './FormContext';
|
||||
|
||||
type FormDetailedProps = Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, 'onChange'> & {
|
||||
disabled?: boolean;
|
||||
onChange?: (value: string | number | boolean | null | undefined, name: string | undefined) => void;
|
||||
};
|
||||
|
||||
export const SubmittingForm = forwardRef<HTMLFormElement, FormDetailedProps>(function SubmittingForm(
|
||||
{
|
||||
disabled,
|
||||
children,
|
||||
onSubmit,
|
||||
onChange = () => {},
|
||||
@@ -34,9 +36,11 @@ export const SubmittingForm = forwardRef<HTMLFormElement, FormDetailedProps>(fun
|
||||
|
||||
return (
|
||||
<form {...rest} ref={ref} onSubmit={handleSubmit}>
|
||||
<FormContext.Provider value={context}>
|
||||
{children}
|
||||
</FormContext.Provider>
|
||||
<fieldset disabled={disabled} className={rest.className}>
|
||||
<FormContext.Provider value={context}>
|
||||
{children}
|
||||
</FormContext.Provider>
|
||||
</fieldset>
|
||||
<button type="submit" hidden />
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ interface Props {
|
||||
leaf?: boolean;
|
||||
className?: string;
|
||||
onExpand?: () => void;
|
||||
onSelect?: (multiple?: boolean) => boolean;
|
||||
onSelect?: (multiple?: boolean) => void;
|
||||
onOpen?: () => void;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const TreeNode: React.FC<Props> = memo(function TreeNode({
|
||||
}, []);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(multiple?: boolean): boolean => handlersRef.current.onSelect?.(multiple) || false,
|
||||
(multiple?: boolean): void => handlersRef.current.onSelect?.(multiple),
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface ITreeNodeContext {
|
||||
loading: boolean;
|
||||
selected: boolean;
|
||||
leaf: boolean;
|
||||
select: (multiple?: boolean) => boolean;
|
||||
select: (multiple?: boolean) => void;
|
||||
expand: () => void;
|
||||
open: () => void;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ export const TreeNodeExpand: React.FC<Props> = function TreeNodeExpand({
|
||||
throw new Error('Context not provided');
|
||||
}
|
||||
|
||||
const handleExpand = () => {
|
||||
const handleExpand = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.stopPropagation();
|
||||
if (!context.leaf && !leaf) {
|
||||
context.expand();
|
||||
}
|
||||
|
||||
@@ -22,10 +22,12 @@ const styles = css`
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TreeNodeSelect: React.FC<Props> = function TreeNodeSelect({
|
||||
disabled,
|
||||
className,
|
||||
}) {
|
||||
const context = useContext(TreeNodeContext);
|
||||
@@ -45,7 +47,7 @@ export const TreeNodeSelect: React.FC<Props> = function TreeNodeSelect({
|
||||
|
||||
return styled(styles)(
|
||||
<div className={className} onClick={handleClick} onDoubleClick={preventPropagation}>
|
||||
<Checkbox checked={context.selected} onChange={() => context.select(true)} />
|
||||
<Checkbox checked={context.selected} disabled={disabled} onChange={() => context.select(true)} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
ResourceKeyUtils,
|
||||
DatabaseConnectionFragment,
|
||||
GetConnectionsQueryVariables,
|
||||
TestConnectionMutation,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -122,10 +123,12 @@ export class ConnectionsResource extends CachedMapResource<string, DatabaseConne
|
||||
return observedConnection;
|
||||
}
|
||||
|
||||
async test(config: ConnectionConfig): Promise<void> {
|
||||
await this.graphQLService.sdk.testConnection({
|
||||
async test(config: ConnectionConfig): Promise<TestConnectionMutation['connection']> {
|
||||
const { connection } = await this.graphQLService.sdk.testConnection({
|
||||
config,
|
||||
});
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
async update(id: string, config: ConnectionConfig): Promise<DatabaseConnection> {
|
||||
|
||||
+8
-2
@@ -78,8 +78,11 @@ export class ConnectionOptionsTabService extends Bootstrap {
|
||||
status.info(`Connection ${connection.name} created`);
|
||||
}
|
||||
} else {
|
||||
await this.connectionsResource.test(config);
|
||||
const info = await this.connectionsResource.test(config);
|
||||
status.info('Connection is established');
|
||||
status.info('Client version: ' + info.clientVersion);
|
||||
status.info('Server version: ' + info.serverVersion);
|
||||
status.info('Connection time: ' + info.connectTime);
|
||||
}
|
||||
} else {
|
||||
if (submitType === 'submit') {
|
||||
@@ -92,8 +95,11 @@ export class ConnectionOptionsTabService extends Bootstrap {
|
||||
status.info(`Connection ${connection.name} created`);
|
||||
}
|
||||
} else {
|
||||
await this.connectionInfoResource.testConnection(config);
|
||||
const info = await this.connectionInfoResource.testConnection(config);
|
||||
status.info('Connection is established');
|
||||
status.info('Client version: ' + info.clientVersion);
|
||||
status.info('Server version: ' + info.serverVersion);
|
||||
status.info('Connection time: ' + info.connectTime);
|
||||
}
|
||||
}
|
||||
} catch (exception) {
|
||||
|
||||
@@ -82,7 +82,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormTabProps> = obse
|
||||
|
||||
data.config.connectionId = data.info.id;
|
||||
|
||||
data.config.name = data.info.name.trim();
|
||||
data.config.name = data.info.name;
|
||||
data.config.description = data.info.description;
|
||||
data.config.template = data.info.template;
|
||||
data.config.driverId = data.info.driverId;
|
||||
@@ -99,13 +99,17 @@ export const Options: TabContainerPanelComponent<IConnectionFormTabProps> = obse
|
||||
data.config.authModelId = data.info.authModel;
|
||||
data.config.saveCredentials = data.info.saveCredentials;
|
||||
|
||||
for (const property of data.info.authProperties) {
|
||||
if (!property.features.includes('password')) {
|
||||
data.config.credentials[property.id!] = property.value;
|
||||
if (data.info.authProperties) {
|
||||
for (const property of data.info.authProperties) {
|
||||
if (!property.features.includes('password')) {
|
||||
data.config.credentials[property.id!] = property.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.config.providerProperties = { ...data.info.providerProperties };
|
||||
if (data.info.providerProperties) {
|
||||
data.config.providerProperties = { ...data.info.providerProperties };
|
||||
}
|
||||
}));
|
||||
const optionsHook = useOptions({ data, form: form.form, options });
|
||||
const { credentialsSavingEnabled } = useAdministrationSettings();
|
||||
@@ -114,11 +118,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormTabProps> = obse
|
||||
DBDriverResource,
|
||||
{ key: data.config.driverId || null, includes: ['includeProviderProperties'] },
|
||||
{
|
||||
onLoad: async resource => {
|
||||
if (data.availableDrivers && data.availableDrivers.length > 1) {
|
||||
await resource.load(resourceKeyList(data.availableDrivers), ['includeProviderProperties']);
|
||||
}
|
||||
|
||||
onLoad: async () => {
|
||||
if (!data.config.driverId && data.info) {
|
||||
data.info.authModel = undefined;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
GetUserConnectionsQueryVariables,
|
||||
ResourceKey,
|
||||
ResourceKeyUtils,
|
||||
TestConnectionMutation,
|
||||
} from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource';
|
||||
@@ -124,10 +125,12 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
|
||||
return this.add(connection);
|
||||
}
|
||||
|
||||
async testConnection(config: ConnectionConfig): Promise<void> {
|
||||
await this.graphQLService.sdk.testConnection({
|
||||
async testConnection(config: ConnectionConfig): Promise<TestConnectionMutation['connection']> {
|
||||
const { connection } = await this.graphQLService.sdk.testConnection({
|
||||
config,
|
||||
});
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
async createFromNode(nodeId: string, nodeName: string): Promise<Connection> {
|
||||
|
||||
@@ -41,9 +41,10 @@ export class NetworkHandlerResource extends CachedMapResource<string, NetworkHan
|
||||
|
||||
async test(config: NetworkHandlerConfigInput): Promise<void> {
|
||||
try {
|
||||
await this.graphQLService.sdk.testNetworkHandler({ config });
|
||||
const { info } = await this.graphQLService.sdk.testNetworkHandler({ config });
|
||||
this.notificationService.logSuccess({
|
||||
title: 'connections_network_handler_test_success',
|
||||
message: 'Client version: ' + info.clientVersion + '\nServer version: ' + info.serverVersion,
|
||||
});
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'connections_network_handler_test_fail');
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import type { IExecutor } from './IExecutor';
|
||||
import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection';
|
||||
@@ -17,24 +17,28 @@ export function useFormValidator(
|
||||
ref: React.RefObject<HTMLFormElement>,
|
||||
callback?: () => void
|
||||
): void {
|
||||
const callbackRef = useRef(callback);
|
||||
callbackRef.current = callback;
|
||||
|
||||
const validate = useCallback(() => {
|
||||
ref.current?.checkValidity();
|
||||
ref.current?.reportValidity();
|
||||
|
||||
if (callbackRef.current) {
|
||||
callbackRef.current();
|
||||
}
|
||||
}, [ref]);
|
||||
const callbackRef = useRef({
|
||||
callback,
|
||||
ref,
|
||||
});
|
||||
callbackRef.current.callback = callback;
|
||||
callbackRef.current.ref = ref;
|
||||
|
||||
useEffect(() => {
|
||||
if (!validationTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (callbackRef.current) {
|
||||
callbackRef.current.ref.current?.checkValidity();
|
||||
callbackRef.current.ref.current?.reportValidity();
|
||||
|
||||
callbackRef.current.callback?.();
|
||||
}
|
||||
}
|
||||
validationTask.addHandler(validate);
|
||||
|
||||
return () => validationTask.removeHandler(validate);
|
||||
}, [validationTask, validate]);
|
||||
}, [validationTask]);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
|
||||
protected async loader(): Promise<ServerConfig> {
|
||||
const { serverConfig } = await this.graphQLService.sdk.serverConfig();
|
||||
|
||||
return serverConfig;
|
||||
return serverConfig as ServerConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
mutation testConnection($config: ConnectionConfig!) {
|
||||
testConnection(config: $config) {
|
||||
connection: testConnection(config: $config) {
|
||||
id
|
||||
connectTime
|
||||
connectionError {
|
||||
message
|
||||
errorCode
|
||||
stackTrace
|
||||
}
|
||||
serverVersion
|
||||
clientVersion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mutation testNetworkHandler($config: NetworkHandlerConfigInput!) {
|
||||
testNetworkHandler(config: $config) {
|
||||
info: testNetworkHandler(config: $config) {
|
||||
message
|
||||
clientVersion
|
||||
serverVersion
|
||||
|
||||
@@ -460,6 +460,16 @@ export interface WebServiceConfig {
|
||||
bundleVersion: Scalars['String'];
|
||||
}
|
||||
|
||||
export interface ProductInfo {
|
||||
id: Scalars['ID'];
|
||||
version: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
buildTime: Scalars['String'];
|
||||
releaseTime: Scalars['String'];
|
||||
licenseInfo?: Maybe<Scalars['String']>;
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
name: Scalars['String'];
|
||||
version: Scalars['String'];
|
||||
@@ -479,6 +489,7 @@ export interface ServerConfig {
|
||||
supportedLanguages: ServerLanguage[];
|
||||
services?: Maybe<Array<Maybe<WebServiceConfig>>>;
|
||||
productConfiguration: Scalars['Object'];
|
||||
productInfo: ProductInfo;
|
||||
defaultNavigatorSettings: NavigatorSettings;
|
||||
}
|
||||
|
||||
@@ -1214,13 +1225,18 @@ export type TestConnectionMutationVariables = Exact<{
|
||||
config: ConnectionConfig;
|
||||
}>;
|
||||
|
||||
export interface TestConnectionMutation { testConnection: Pick<ConnectionInfo, 'id'> }
|
||||
export interface TestConnectionMutation {
|
||||
connection: (
|
||||
Pick<ConnectionInfo, 'id' | 'connectTime' | 'serverVersion' | 'clientVersion'>
|
||||
& { connectionError?: Maybe<Pick<ServerError, 'message' | 'errorCode' | 'stackTrace'>> }
|
||||
);
|
||||
}
|
||||
|
||||
export type TestNetworkHandlerMutationVariables = Exact<{
|
||||
config: NetworkHandlerConfigInput;
|
||||
}>;
|
||||
|
||||
export interface TestNetworkHandlerMutation { testNetworkHandler: Pick<NetworkEndpointInfo, 'message' | 'clientVersion' | 'serverVersion'> }
|
||||
export interface TestNetworkHandlerMutation { info: Pick<NetworkEndpointInfo, 'message' | 'clientVersion' | 'serverVersion'> }
|
||||
|
||||
export type UpdateConnectionMutationVariables = Exact<{
|
||||
config: ConnectionConfig;
|
||||
@@ -2046,14 +2062,22 @@ export const SetConnectionNavigatorSettingsDocument = `
|
||||
${DatabaseConnectionFragmentDoc}`;
|
||||
export const TestConnectionDocument = `
|
||||
mutation testConnection($config: ConnectionConfig!) {
|
||||
testConnection(config: $config) {
|
||||
connection: testConnection(config: $config) {
|
||||
id
|
||||
connectTime
|
||||
connectionError {
|
||||
message
|
||||
errorCode
|
||||
stackTrace
|
||||
}
|
||||
serverVersion
|
||||
clientVersion
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const TestNetworkHandlerDocument = `
|
||||
mutation testNetworkHandler($config: NetworkHandlerConfigInput!) {
|
||||
testNetworkHandler(config: $config) {
|
||||
info: testNetworkHandler(config: $config) {
|
||||
message
|
||||
clientVersion
|
||||
serverVersion
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
.theme-button {
|
||||
composes: mdc-button;
|
||||
height: 28px;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.theme-button_ripple {
|
||||
composes: mdc-button__ripple;
|
||||
|
||||
@@ -7,7 +7,17 @@
|
||||
*/
|
||||
|
||||
@mixin form-controls() {
|
||||
input {
|
||||
:global(fieldset) {
|
||||
padding: 0;
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
||||
&:global([disabled]) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
input:not([role="new"]) {
|
||||
@include mdc-theme-prop(color, on-secondary, false);
|
||||
@include mdc-theme-prop(background-color, secondary, false);
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -45,7 +45,7 @@ export const User: React.FC<Props> = observer(function User({ user, selectable }
|
||||
</TableColumnValue>
|
||||
<TableColumnValue expand>{user.userId}</TableColumnValue>
|
||||
<TableColumnValue>{user.grantedRoles.join(', ')}</TableColumnValue>
|
||||
<TableColumnValue>
|
||||
<TableColumnValue flex>
|
||||
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} context={user} />
|
||||
</TableColumnValue>
|
||||
</TableItem>
|
||||
|
||||
@@ -60,7 +60,8 @@ export class AuthenticationService extends Bootstrap {
|
||||
}
|
||||
|
||||
private async requireAuthentication() {
|
||||
if (!await this.appAuthService.isAuthNeeded()) {
|
||||
const authNeeded = await this.appAuthService.isAuthNeeded();
|
||||
if (!authNeeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +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 { Connection, ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ConnectionConfig, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class CustomConnectionService {
|
||||
constructor(
|
||||
private graphQLService: GraphQLService,
|
||||
private connectionInfoResource: ConnectionInfoResource
|
||||
) {
|
||||
}
|
||||
|
||||
async createConnectionAsync(config: ConnectionConfig): Promise<Connection> {
|
||||
return this.connectionInfoResource.createConnection(config);
|
||||
}
|
||||
|
||||
async testConnectionAsync(config: ConnectionConfig): Promise<void> {
|
||||
await this.graphQLService.sdk.testConnection({
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@
|
||||
import type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { CustomConnectionPluginBootstrap } from './CustomConnectionPluginBootstrap';
|
||||
import { CustomConnectionService } from './CustomConnectionService';
|
||||
import { LocaleService } from './LocaleService';
|
||||
|
||||
export const customConnectionPluginManifest: PluginManifest = {
|
||||
@@ -18,7 +17,6 @@ export const customConnectionPluginManifest: PluginManifest = {
|
||||
},
|
||||
|
||||
providers: [
|
||||
CustomConnectionService,
|
||||
LocaleService,
|
||||
CustomConnectionPluginBootstrap,
|
||||
],
|
||||
|
||||
+1
-7
@@ -62,13 +62,7 @@ export const PublicConnectionForm: React.FC = observer(function PublicConnection
|
||||
const service = useService(PublicConnectionFormService);
|
||||
|
||||
const close = useCallback(() => service.close(), []);
|
||||
const save = useCallback((config: ConnectionConfig) => {
|
||||
if (config.connectionId) {
|
||||
service.change({ connectionId: config.connectionId });
|
||||
} else {
|
||||
service.close(true);
|
||||
}
|
||||
}, []);
|
||||
const save = useCallback(() => service.close(true), []);
|
||||
|
||||
return styled(styles)(
|
||||
<Loader loading={service.dataOptions !== null}>
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export class PublicConnectionFormBootstrap extends Bootstrap {
|
||||
return context.contextType === NavigationTreeContextMenuService.nodeContextType
|
||||
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
|
||||
},
|
||||
title: 'Edit',
|
||||
title: 'connections_public_connection_edit_menu_item_title',
|
||||
order: 2,
|
||||
onClick: context => {
|
||||
const node = context.data;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['connections_public_connection_edit_menu_item_title', 'Edit Connection'],
|
||||
['connections_public_connection_edit_cancel_title', 'Cancel confirmation'],
|
||||
['connections_public_connection_edit_cancel_message', 'You\'re going to cancel connection changes. Unsaved changes will be lost. Are you sure?'],
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default [
|
||||
['connections_public_connection_edit_menu_item_title', 'Изменить подключение'],
|
||||
['connections_public_connection_edit_cancel_title', 'Отмена редактирования'],
|
||||
['connections_public_connection_edit_cancel_message', 'Вы собираетесь закрыть редактор, несохраненные изменения не будут применены. Вы уверены?'],
|
||||
];
|
||||
|
||||
@@ -89,8 +89,10 @@ export const CellEditor = observer<Pick<EditorProps<any, any>, 'rowIdx' | 'row'
|
||||
const type = typeof tableDataContext?.getCellValue(rowIdx, column.key) === 'number' ? 'number' : 'text';
|
||||
|
||||
const handleSave = () => {
|
||||
dataGridContext.model.source.getEditor(dataGridContext.resultIndex)
|
||||
.setCell(rowIdx, Number(column.key), Number(value));
|
||||
if (type === 'number') {
|
||||
const editor = dataGridContext.model.source.getEditor(dataGridContext.resultIndex);
|
||||
editor.setCell(rowIdx, Number(column.key), Number(editor.getCell(rowIdx, Number(column.key))));
|
||||
}
|
||||
onClose(false);
|
||||
};
|
||||
const handleReject = () => {
|
||||
|
||||
@@ -69,7 +69,7 @@ export class DatabaseDataEditor<TResult extends IDatabaseDataResult> implements
|
||||
set: this.set.bind(this, result),
|
||||
setCell: this.setCell.bind(this, result),
|
||||
get: this.get.bind(this, result),
|
||||
getCell: this.get.bind(this, result),
|
||||
getCell: this.getCell.bind(this, result),
|
||||
isCellEdited: this.isCellEdited.bind(this, result),
|
||||
isEdited: this.isResultEdited.bind(this, result),
|
||||
isRowEdited: this.isRowEdited.bind(this, result),
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
*/
|
||||
|
||||
import { NavigationTabsService } from '@cloudbeaver/core-app';
|
||||
import { ConnectionsManagerService, ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { ConnectionsManagerService } from '@cloudbeaver/core-connections';
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { IExecutor, Executor, IExecutionContextProvider } from '@cloudbeaver/core-executor';
|
||||
import { NavigationService } from '@cloudbeaver/core-ui';
|
||||
|
||||
import type { ISqlEditorTabState } from './ISqlEditorTabState';
|
||||
import { SqlEditorTabService, isSQLEditorTab } from './SqlEditorTabService';
|
||||
@@ -49,13 +50,14 @@ export class SqlEditorNavigatorService {
|
||||
private navigationTabsService: NavigationTabsService,
|
||||
private connectionsManagerService: ConnectionsManagerService,
|
||||
private notificationService: NotificationService,
|
||||
private connectionInfoResource: ConnectionInfoResource,
|
||||
private sqlEditorTabService: SqlEditorTabService
|
||||
private sqlEditorTabService: SqlEditorTabService,
|
||||
navigationService: NavigationService
|
||||
) {
|
||||
this.navigator = new Executor<SQLCreateAction | SQLEditorAction>(
|
||||
null,
|
||||
(active, current) => active.type === current.type
|
||||
)
|
||||
.before(navigationService.navigationTask)
|
||||
.addHandler(this.navigateHandler.bind(this));
|
||||
this.connectionsManagerService.onCloseConnection.subscribe(this.handleConnectionClose.bind(this));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user