mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'devel' into feat/roles
This commit is contained in:
@@ -83,6 +83,9 @@ public interface DBWSecurityController {
|
||||
|
||||
WebRole[] findRoles(String roleName) throws DBCException;
|
||||
|
||||
@NotNull
|
||||
String[] getRoleSubjects(String roleId) throws DBCException;
|
||||
|
||||
void createRole(WebRole role) throws DBCException;
|
||||
|
||||
void updateRole(WebRole role) throws DBCException;
|
||||
|
||||
+20
@@ -433,6 +433,26 @@ class CBSecurityController implements DBWSecurityController {
|
||||
return readAllRoles();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String[] getRoleSubjects(String roleId) throws DBCException {
|
||||
try (Connection dbCon = database.openConnection()) {
|
||||
try (PreparedStatement dbStat = dbCon.prepareStatement(
|
||||
"SELECT USER_ID FROM CB_USER_ROLE WHERE ROLE_ID")) {
|
||||
dbStat.setString(1, roleId);
|
||||
List<String> subjects = new ArrayList<>();
|
||||
try (ResultSet dbResult = dbStat.executeQuery()) {
|
||||
while (dbResult.next()) {
|
||||
subjects.add(dbResult.getString(1));
|
||||
}
|
||||
}
|
||||
return subjects.toArray(new String[0]);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error while reading role subjects", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private WebRole fetchRole(ResultSet dbResult) throws SQLException {
|
||||
WebRole role = new WebRole(dbResult.getString("ROLE_ID"));
|
||||
|
||||
@@ -36,6 +36,9 @@ type AdminRoleInfo {
|
||||
roleName: String
|
||||
description: String
|
||||
|
||||
grantedUsers: [ID!]!
|
||||
grantedConnections: [AdminConnectionGrantInfo!]!
|
||||
|
||||
rolePermissions: [ID]!
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -16,7 +16,11 @@
|
||||
*/
|
||||
package io.cloudbeaver.service.admin;
|
||||
|
||||
import io.cloudbeaver.DBWConnectionGrant;
|
||||
import io.cloudbeaver.model.user.WebRole;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
import org.jkiss.dbeaver.model.exec.DBCException;
|
||||
import org.jkiss.dbeaver.model.meta.Property;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -51,4 +55,15 @@ public class AdminRoleInfo {
|
||||
public void setRolePermissions(List<String> rolePermissions) {
|
||||
this.rolePermissions = rolePermissions;
|
||||
}
|
||||
|
||||
@Property
|
||||
public DBWConnectionGrant[] getGrantedConnections() throws DBCException {
|
||||
return CBPlatform.getInstance().getApplication().getSecurityController().getSubjectConnectionAccess(new String[] { getRoleId()} );
|
||||
}
|
||||
|
||||
@Property
|
||||
public String[] getGrantedUsers() throws DBCException {
|
||||
return CBPlatform.getInstance().getApplication().getSecurityController().getRoleSubjects(getRoleId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-4
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
@@ -75,12 +75,12 @@ export const NavigationNodeControl = observer<Props>(function NavigationNodeCont
|
||||
}) {
|
||||
const context = useContext(TreeNodeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const outdated = navNodeInfoResource.isOutdated(node.id) && !context?.loading;
|
||||
const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !context.loading);
|
||||
|
||||
const connected = node.objectFeatures.includes(EObjectFeature.dataSourceConnected);
|
||||
|
||||
const onClickHandler = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
context?.select(event.ctrlKey || event.metaKey);
|
||||
context.select(event.ctrlKey || event.metaKey);
|
||||
}, [context]);
|
||||
|
||||
return styled(useStyles(TREE_NODE_STYLES, styles))(
|
||||
@@ -91,7 +91,7 @@ export const NavigationNodeControl = observer<Props>(function NavigationNodeCont
|
||||
</TreeNodeIcon>
|
||||
<TreeNodeName>{node.name}</TreeNodeName>
|
||||
<portal>
|
||||
<TreeNodeMenu node={node} selected={context?.selected} />
|
||||
<TreeNodeMenu node={node} selected={context.selected} />
|
||||
</portal>
|
||||
</TreeNodeControl>
|
||||
);
|
||||
|
||||
@@ -40,6 +40,13 @@ function sortManageable(connectionInfoResource: ConnectionInfoResource): (nodeA:
|
||||
const connectionA = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeA.id));
|
||||
const connectionB = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeB.id));
|
||||
|
||||
if (!connectionA || !connectionB) {
|
||||
if (connectionA === connectionB) {
|
||||
return 0;
|
||||
}
|
||||
return connectionA ? 1 : -1;
|
||||
}
|
||||
|
||||
const nodeAManageable = connectionA?.features.includes(EConnectionFeature.manageable);
|
||||
const nodeBManageable = connectionB?.features.includes(EConnectionFeature.manageable);
|
||||
|
||||
@@ -71,7 +78,7 @@ export function navigationTreeConnectionGroupFilter(
|
||||
|
||||
for (const node of nodes) {
|
||||
const connection = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(node.id));
|
||||
const manageable = connection?.features.includes(EConnectionFeature.manageable);
|
||||
const manageable = !!connection?.features.includes(EConnectionFeature.manageable);
|
||||
|
||||
let nextGroup = NAVIGATION_TREE_CONNECTION_GROUPS.unsorted;
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
width: 400px;
|
||||
min-width: auto;
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -42,6 +38,7 @@ export const SessionExpiredDialog: DialogComponent<null, null> = observer(functi
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='small'
|
||||
title={title}
|
||||
footer={(
|
||||
<controls>
|
||||
@@ -54,6 +51,7 @@ export const SessionExpiredDialog: DialogComponent<null, null> = observer(functi
|
||||
</Button>
|
||||
</controls>
|
||||
)}
|
||||
fixedSize
|
||||
noOverflow
|
||||
onReject={rejectDialog}
|
||||
>
|
||||
|
||||
+2
-4
@@ -15,10 +15,6 @@ import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
width: 400px;
|
||||
min-width: auto;
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -39,6 +35,7 @@ export const SessionExpireWarningDialog: DialogComponent<null, null> = observer(
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='small'
|
||||
title={translate('app_root_session_expire_warning_title')}
|
||||
footer={(
|
||||
<controls>
|
||||
@@ -51,6 +48,7 @@ export const SessionExpireWarningDialog: DialogComponent<null, null> = observer(
|
||||
</Button>
|
||||
</controls>
|
||||
)}
|
||||
fixedSize
|
||||
noOverflow
|
||||
onReject={rejectDialog}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
import type React from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { ENotificationType } from '../../core-events/src';
|
||||
import { ENotificationType } from '@cloudbeaver/core-events';
|
||||
|
||||
import { NotificationMark } from './Snackbars/NotificationMark';
|
||||
|
||||
const style = css`
|
||||
@@ -17,7 +18,6 @@ const style = css`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: auto;
|
||||
|
||||
&[|root] {
|
||||
|
||||
@@ -21,4 +21,4 @@ export interface ITreeNodeContext {
|
||||
open: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const TreeNodeContext = createContext<ITreeNodeContext | null>(null);
|
||||
export const TreeNodeContext = createContext<ITreeNodeContext>(undefined as any);
|
||||
|
||||
@@ -32,10 +32,6 @@ export const TreeNodeControl = observer<Props>(function TreeNodeControl({
|
||||
}) {
|
||||
const context = useContext(TreeNodeContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Context not provided');
|
||||
}
|
||||
|
||||
const handleEnter = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (EventContext.has(event, EventTreeNodeExpandFlag, EventTreeNodeSelectFlag)) {
|
||||
return;
|
||||
@@ -44,7 +40,7 @@ export const TreeNodeControl = observer<Props>(function TreeNodeControl({
|
||||
EventContext.set(event, EventTreeNodeSelectFlag);
|
||||
switch ((event as unknown as KeyboardEvent).code) {
|
||||
case KEY.ENTER:
|
||||
context?.select(event.ctrlKey || event.metaKey);
|
||||
context.select(event.ctrlKey || event.metaKey);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
import { css } from 'reshadow';
|
||||
|
||||
import { Icon, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, Icon, Loader } from '@cloudbeaver/core-blocks';
|
||||
import { EventContext } from '@cloudbeaver/core-events';
|
||||
|
||||
import { useStateDelay } from '../../useStateDelay';
|
||||
@@ -39,10 +39,6 @@ export const TreeNodeExpand = observer<Props>(function TreeNodeExpand({
|
||||
}) {
|
||||
const context = useContext(TreeNodeContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Context not provided');
|
||||
}
|
||||
|
||||
const handleExpand = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
EventContext.set(event, EventTreeNodeExpandFlag);
|
||||
|
||||
@@ -55,7 +51,7 @@ export const TreeNodeExpand = observer<Props>(function TreeNodeExpand({
|
||||
EventContext.set(event, EventTreeNodeExpandFlag);
|
||||
};
|
||||
|
||||
const loading = useStateDelay(context.loading || context.processing, 300);
|
||||
const loading = useStateDelay(getComputed(() => context.loading || context.processing), 300);
|
||||
|
||||
return styled(styles)(
|
||||
<arrow className={className} onClick={handleExpand} onDoubleClick={handleDbClick}>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* 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 } from 'mobx';
|
||||
|
||||
export function getComputed<T>(action: () => T): T {
|
||||
return computed(action).get();
|
||||
}
|
||||
@@ -133,6 +133,7 @@ export * from './Cell';
|
||||
export * from './ErrorMessage';
|
||||
export * from './StatusMessage';
|
||||
export * from './ExceptionMessage';
|
||||
export * from './getComputed';
|
||||
export * from './IconButton';
|
||||
export * from './IconOrImage';
|
||||
export * from './StaticImage';
|
||||
|
||||
@@ -13,6 +13,9 @@ export function useObjectRef<T extends Record<any, any>>(
|
||||
update: false,
|
||||
bind?: Array<keyof T>
|
||||
): T;
|
||||
export function useObjectRef<T extends Record<any, any>>(
|
||||
init: () => T & ThisType<T>
|
||||
): T;
|
||||
export function useObjectRef<T extends Record<any, any>, U extends Record<any, any>>(
|
||||
init: () => (T & ThisType<T & U>),
|
||||
update: U & ThisType<T & U>,
|
||||
@@ -37,7 +40,7 @@ export function useObjectRef<T extends Record<any, any>, U extends Record<any, a
|
||||
update = undefined;
|
||||
}
|
||||
|
||||
if (update === undefined) {
|
||||
if (update === undefined && arguments.length === 1) {
|
||||
update = typeof init === 'function' ? init() : init;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,12 @@ module.exports = {
|
||||
modules: false,
|
||||
targets: {
|
||||
browsers: [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 edge version",
|
||||
"last 1 safari version"
|
||||
"defaults",
|
||||
"not IE 11",
|
||||
// "last 1 chrome version",
|
||||
// "last 1 firefox version",
|
||||
// "last 1 edge version",
|
||||
// "last 1 safari version"
|
||||
]
|
||||
},
|
||||
exclude: ["transform-async-to-generator", "transform-regenerator"],
|
||||
|
||||
+1
-4
@@ -19,10 +19,6 @@ import { useDBDriver } from '../useDBDriver';
|
||||
import { ConnectionAuthenticationForm } from './ConnectionAuthenticationForm';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
min-height: 400px;
|
||||
min-width: 500px;
|
||||
}
|
||||
SubmittingForm {
|
||||
overflow: auto;
|
||||
margin: auto;
|
||||
@@ -56,6 +52,7 @@ export const ConnectionAuthenticationDialog: DialogComponent<Payload> = observer
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('connections_connection_credentials_provisioning')}
|
||||
subTitle={translate('connections_connection_credentials_provisioning_description')}
|
||||
icon={driver?.icon}
|
||||
|
||||
@@ -34,10 +34,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
min-height: 400px;
|
||||
min-width: 500px;
|
||||
}
|
||||
SubmittingForm {
|
||||
overflow: auto;
|
||||
margin: auto;
|
||||
@@ -79,6 +75,7 @@ export const DatabaseAuthDialog: DialogComponent<Payload> = observer(function Da
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('connections_database_authentication')}
|
||||
subTitle={connection.connectionInfo?.name}
|
||||
icon={driver?.icon}
|
||||
|
||||
@@ -14,10 +14,12 @@ import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { commonDialogBaseStyle, commonDialogThemeStyle } from './styles';
|
||||
|
||||
export interface CommonDialogWrapperProps {
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
title?: string;
|
||||
subTitle?: string;
|
||||
icon?: string;
|
||||
viewBox?: string;
|
||||
fixedSize?: boolean;
|
||||
bigIcon?: boolean;
|
||||
noBodyPadding?: boolean;
|
||||
noOverflow?: boolean;
|
||||
@@ -28,6 +30,8 @@ export interface CommonDialogWrapperProps {
|
||||
}
|
||||
|
||||
export const CommonDialogWrapper: React.FC<CommonDialogWrapperProps> = function CommonDialogWrapper({
|
||||
size = 'medium',
|
||||
fixedSize,
|
||||
title,
|
||||
subTitle,
|
||||
icon,
|
||||
@@ -41,7 +45,7 @@ export const CommonDialogWrapper: React.FC<CommonDialogWrapperProps> = function
|
||||
children,
|
||||
}) {
|
||||
return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle))(
|
||||
<dialog className={className}>
|
||||
<dialog className={className} {...use({ size, fixedSize })}>
|
||||
<header>
|
||||
<icon-container>
|
||||
{icon && <IconOrImage {...use({ bigIcon })} icon={icon} viewBox={viewBox} />}
|
||||
|
||||
@@ -44,12 +44,43 @@ export const commonDialogBaseStyle = composes(
|
||||
margin: 0;
|
||||
border: none;
|
||||
height: auto;
|
||||
min-width: 748px;
|
||||
max-height: 100%;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0px;
|
||||
|
||||
&[|size=small] {
|
||||
min-width: 404px;
|
||||
min-height: 262px;
|
||||
max-height: max(100vh - 48px, 262px);
|
||||
|
||||
&[|fixedSize] {
|
||||
width: 404px;
|
||||
height: 262px;
|
||||
}
|
||||
}
|
||||
&[|size=medium] {
|
||||
min-width: 576px;
|
||||
min-height: 374px;
|
||||
max-height: max(100vh - 48px, 374px);
|
||||
|
||||
&[|fixedSize] {
|
||||
width: 576px;
|
||||
height: 374px;
|
||||
}
|
||||
}
|
||||
&[|size=large] {
|
||||
min-width: 720px;
|
||||
min-height: 468px;
|
||||
max-height: max(100vh - 48px, 468px);
|
||||
|
||||
&[|fixedSize] {
|
||||
width: 720px;
|
||||
height: 468px;
|
||||
}
|
||||
}
|
||||
}
|
||||
header, dialog-body, footer {
|
||||
flex-shrink: 0;
|
||||
padding: 24px;
|
||||
|
||||
&[|no-padding] {
|
||||
@@ -58,6 +89,7 @@ export const commonDialogBaseStyle = composes(
|
||||
}
|
||||
dialog-body {
|
||||
padding-top: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
footer {
|
||||
padding-top: 0px;
|
||||
@@ -89,41 +121,42 @@ export const commonDialogBaseStyle = composes(
|
||||
}
|
||||
}
|
||||
dialog-body {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
max-width: 748px;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
dialog-body-overflow-box {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding-right: 24px;
|
||||
}
|
||||
dialog-body-content {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
dialog-body[|no-padding] + footer {
|
||||
padding-top: 24px;
|
||||
}
|
||||
dialog-body[|no-padding] dialog-body-overflow-box {
|
||||
padding-right: 0;
|
||||
}
|
||||
dialog-body[|no-overflow] dialog-body-content {
|
||||
padding-bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
dialog-body-overflow {
|
||||
position: absolute;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
pointer-events: none;
|
||||
|
||||
@@ -50,7 +50,7 @@ export const ConfirmationDialog: DialogComponent<ConfirmationDialogPayload> = fu
|
||||
const { icon, title, subTitle, bigIcon, viewBox, message, confirmActionText } = payload;
|
||||
|
||||
return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle, style))(
|
||||
<dialog className={className}>
|
||||
<dialog className={className} {...use({ size: 'small' })}>
|
||||
<header>
|
||||
<icon-container>
|
||||
{icon && <IconOrImage {...use({ bigIcon })} icon={icon} viewBox={viewBox} />}
|
||||
@@ -65,7 +65,7 @@ export const ConfirmationDialog: DialogComponent<ConfirmationDialogPayload> = fu
|
||||
</header-title>
|
||||
{subTitle && <sub-title>{subTitle}</sub-title>}
|
||||
</header>
|
||||
<dialog-body {...use({ 'no-overflow': true })}>
|
||||
<dialog-body>
|
||||
<dialog-body-overflow-box>
|
||||
<dialog-body-content>
|
||||
<Translate token={message} />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogBackdrop,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from 'reakit/Dialog';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
@@ -22,87 +23,85 @@ import { CommonDialogService, DialogInternal } from './CommonDialogService';
|
||||
import { dialogStyles } from './styles';
|
||||
|
||||
export const DialogsPortal = observer(function DialogsPortal() {
|
||||
const styles = useStyles(dialogStyles);
|
||||
const commonDialogService = useService(CommonDialogService);
|
||||
const handleReject = useCallback(
|
||||
(dialog: DialogInternal<any>) => commonDialogService.rejectDialog(dialog.promise),
|
||||
[commonDialogService]
|
||||
);
|
||||
const handleResolve = useCallback(
|
||||
(dialog: DialogInternal<any>, result: any) => commonDialogService.resolveDialog(dialog.promise, result),
|
||||
[commonDialogService]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{commonDialogService.dialogs.map((dialog, i, arr) => (
|
||||
<NestedDialog
|
||||
key={i}
|
||||
visible={i === arr.length - 1}
|
||||
dialog={dialog}
|
||||
resolveDialog={handleResolve}
|
||||
rejectDialog={handleReject}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
let activeDialog: DialogInternal<any> | undefined;
|
||||
|
||||
if (commonDialogService.dialogs.length > 0) {
|
||||
activeDialog = commonDialogService.dialogs[commonDialogService.dialogs.length - 1];
|
||||
}
|
||||
|
||||
const state = useObjectRef(() => ({
|
||||
reject() {
|
||||
if (this.dialog) {
|
||||
commonDialogService.rejectDialog(this.dialog.promise);
|
||||
}
|
||||
},
|
||||
resolve(result: any) {
|
||||
if (this.dialog) {
|
||||
commonDialogService.resolveDialog(this.dialog.promise, result);
|
||||
}
|
||||
},
|
||||
backdropClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
if (!this.dialog?.options?.persistent && e.currentTarget.isEqualNode(e.target as HTMLElement)) {
|
||||
this.reject();
|
||||
}
|
||||
},
|
||||
}), {
|
||||
dialog: activeDialog,
|
||||
}, ['reject', 'resolve', 'backdropClick']);
|
||||
|
||||
return styled(styles)(
|
||||
<DialogBackdrop visible={!!activeDialog} onMouseDown={state.backdropClick}>
|
||||
<inner-box>
|
||||
{commonDialogService.dialogs.map((dialog, i, arr) => (
|
||||
<NestedDialog
|
||||
key={i}
|
||||
visible={i === arr.length - 1}
|
||||
dialog={dialog}
|
||||
resolveDialog={state.resolve}
|
||||
rejectDialog={state.reject}
|
||||
/>
|
||||
))}
|
||||
</inner-box>
|
||||
</DialogBackdrop>
|
||||
);
|
||||
});
|
||||
|
||||
interface NestedDialogType {
|
||||
dialog: DialogInternal<any>;
|
||||
resolveDialog: (dialog: DialogInternal<any>, result: any) => void;
|
||||
rejectDialog: (dialog: DialogInternal<any>) => void;
|
||||
resolveDialog: (result: any) => void;
|
||||
rejectDialog: () => void;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
function NestedDialog({
|
||||
const NestedDialog: React.FC<NestedDialogType> = function NestedDialog({
|
||||
dialog,
|
||||
resolveDialog,
|
||||
rejectDialog,
|
||||
visible,
|
||||
}: NestedDialogType) {
|
||||
const lastVisibility = useRef(visible);
|
||||
const dialogState = useDialogState({ visible });
|
||||
}) {
|
||||
const dialogState = useDialogState({ visible: true });
|
||||
const styles = useStyles(dialogStyles);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialogState.visible
|
||||
&& dialogState.visible !== lastVisibility.current
|
||||
&& !dialog.options?.persistent
|
||||
) {
|
||||
rejectDialog(dialog);
|
||||
} else {
|
||||
lastVisibility.current = visible;
|
||||
dialogState.setVisible(visible);
|
||||
if (!dialogState.visible && !dialog.options?.persistent) {
|
||||
rejectDialog();
|
||||
}
|
||||
});
|
||||
|
||||
const handleReject = useCallback(() => rejectDialog(dialog), [dialog, rejectDialog]);
|
||||
const handleResolve = useCallback(
|
||||
(result: any) => resolveDialog(dialog, result),
|
||||
[dialog, resolveDialog]
|
||||
);
|
||||
|
||||
const DialogComponent = dialog.component;
|
||||
|
||||
const backdropClickHandler = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
if (!dialog.options?.persistent && e.currentTarget.isEqualNode(e.target as HTMLElement)) {
|
||||
rejectDialog(dialog);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: place Dialog inside CommonDialogWrapper, so we can pass aria-label
|
||||
return styled(styles)(
|
||||
<>
|
||||
<DialogBackdrop {...dialogState} onMouseDown={backdropClickHandler}>
|
||||
<Dialog {...dialogState} hideOnClickOutside={false}>
|
||||
<DialogComponent
|
||||
payload={dialog.payload}
|
||||
options={dialog.options}
|
||||
resolveDialog={handleResolve}
|
||||
rejectDialog={handleReject}
|
||||
/>
|
||||
</Dialog>
|
||||
</DialogBackdrop>
|
||||
</>
|
||||
<Dialog {...dialogState} visible={visible} hideOnClickOutside={false} modal={false}>
|
||||
<DialogComponent
|
||||
payload={dialog.payload}
|
||||
options={dialog.options}
|
||||
resolveDialog={resolveDialog}
|
||||
rejectDialog={rejectDialog}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,15 +21,20 @@ export const dialogStyles = css`
|
||||
left: 0px;
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
inner-box {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
padding: 24px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
Dialog {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
max-height: calc(100vh);
|
||||
z-index: 999;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ export const ErrorDetailsDialog: DialogComponent<Error | string, null> = observe
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('core_eventsLog_dbeaverErrorDetails')}
|
||||
icon='/icons/error_icon.svg'
|
||||
footer={(
|
||||
|
||||
+2
-1
@@ -25,8 +25,9 @@ export const styles = css`
|
||||
margin-left: 24px;
|
||||
}
|
||||
textarea {
|
||||
width: 100% !important;
|
||||
width: 100%;
|
||||
min-height: 270px;
|
||||
resize: vertical;
|
||||
}
|
||||
message {
|
||||
overflow: auto;
|
||||
|
||||
+34
-10
@@ -13,7 +13,8 @@ import styled, { css } from 'reshadow';
|
||||
import { AuthConfigurationParametersResource, AuthProvidersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
BASE_CONTAINERS_STYLES, ColoredContainer, ComboboxNew, FieldCheckboxNew, Group, GroupTitle,
|
||||
InputFieldNew, ObjectPropertyInfoFormNew, SubmittingForm, TabContainerPanelComponent, TextareaNew, useMapResource
|
||||
InputFieldNew, ObjectPropertyInfoFormNew, SubmittingForm, TabContainerPanelComponent,
|
||||
TextareaNew, useMapResource, useObjectPropertyCategories
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import type { AuthProviderConfigurationParametersFragment } from '@cloudbeaver/core-sdk';
|
||||
@@ -28,6 +29,8 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
const emptyArray: AuthProviderConfigurationParametersFragment[] = [];
|
||||
|
||||
export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigurationFormProps> = observer(function AuthConfigurationOptions({
|
||||
state,
|
||||
}) {
|
||||
@@ -39,6 +42,10 @@ export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigura
|
||||
const providers = useMapResource(AuthProvidersResource, AuthProvidersResource.keyAll);
|
||||
const parameters = useMapResource(AuthConfigurationParametersResource, state.config.providerId || null);
|
||||
|
||||
const { categories, isUncategorizedExists } = useObjectPropertyCategories(
|
||||
(parameters.data as AuthProviderConfigurationParametersFragment[] | undefined) ?? emptyArray
|
||||
);
|
||||
|
||||
const identityProviders = providers.resource.values.filter(provider => provider.configurable);
|
||||
const edit = state.mode === 'edit';
|
||||
|
||||
@@ -111,15 +118,32 @@ export const AuthConfigurationOptions: TabContainerPanelComponent<IAuthConfigura
|
||||
</FieldCheckboxNew>
|
||||
</Group>
|
||||
{parameters.isLoaded() && parameters.data && (
|
||||
<Group small gap vertical>
|
||||
<GroupTitle>{translate('administration_identity_providers_provider_configuration_parameters')}</GroupTitle>
|
||||
<ObjectPropertyInfoFormNew
|
||||
state={state.config.parameters}
|
||||
properties={parameters.data as AuthProviderConfigurationParametersFragment[]}
|
||||
disabled={state.disabled}
|
||||
readOnly={state.readonly}
|
||||
/>
|
||||
</Group>
|
||||
<>
|
||||
{isUncategorizedExists && (
|
||||
<Group small gap vertical>
|
||||
<GroupTitle>{translate('administration_identity_providers_provider_configuration_parameters')}</GroupTitle>
|
||||
<ObjectPropertyInfoFormNew
|
||||
state={state.config.parameters}
|
||||
properties={parameters.data as AuthProviderConfigurationParametersFragment[]}
|
||||
category={null}
|
||||
disabled={state.disabled}
|
||||
readOnly={state.readonly}
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
{categories.map(category => (
|
||||
<Group key={category} small gap vertical>
|
||||
<GroupTitle>{category}</GroupTitle>
|
||||
<ObjectPropertyInfoFormNew
|
||||
state={state.config.parameters}
|
||||
properties={parameters.data as AuthProviderConfigurationParametersFragment[]}
|
||||
category={category}
|
||||
disabled={state.disabled}
|
||||
readOnly={state.readonly}
|
||||
/>
|
||||
</Group>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ColoredContainer>
|
||||
</SubmittingForm>
|
||||
|
||||
@@ -33,11 +33,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
min-height: 400px;
|
||||
min-width: 500px;
|
||||
max-width: min-content;
|
||||
}
|
||||
SubmittingForm {
|
||||
overflow: auto;
|
||||
&[|form] {
|
||||
@@ -119,6 +114,7 @@ export const AuthDialog: DialogComponent<IAuthPayload, null> = observer(function
|
||||
return styled(useStyles(styles, UNDERLINE_TAB_STYLES))(
|
||||
<TabsState currentTabId={controller.provider?.id}>
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={dialogTitle}
|
||||
icon={controller.provider?.icon}
|
||||
subTitle={subTitle}
|
||||
@@ -136,6 +132,7 @@ export const AuthDialog: DialogComponent<IAuthPayload, null> = observer(function
|
||||
)}
|
||||
</AuthDialogFooter>
|
||||
)}
|
||||
noBodyPadding
|
||||
onReject={options?.persistent ? undefined : rejectDialog}
|
||||
>
|
||||
{showTabs && (
|
||||
|
||||
+1
-3
@@ -29,9 +29,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
min-width: 500px;
|
||||
}
|
||||
ErrorMessage {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -93,6 +90,7 @@ export const ChangeUserPasswordDialog: DialogComponent<null, null> = observer(
|
||||
|
||||
return styled(style)(
|
||||
<CommonDialogWrapper
|
||||
size='medium'
|
||||
title={translate('authentication_user_password_change_dialog_title')}
|
||||
footer={(
|
||||
<ChangeUserPasswordDialogFooter
|
||||
|
||||
+2
-4
@@ -16,10 +16,6 @@ import type { IDriver } from './Driver';
|
||||
import { DriverSelector } from './DriverSelector';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 550px;
|
||||
min-height: 550px;
|
||||
}
|
||||
DriverSelector {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -42,7 +38,9 @@ export const DriverSelectorDialog = observer<IProps>(function DriverSelectorDial
|
||||
}) {
|
||||
return styled(styles)(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={title}
|
||||
fixedSize
|
||||
noBodyPadding
|
||||
noOverflow
|
||||
onReject={onClose}
|
||||
|
||||
+3
-5
@@ -35,10 +35,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 600px;
|
||||
min-height: 500px;
|
||||
}
|
||||
SubmittingForm, center {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -73,6 +69,7 @@ export const ConnectionDialog: DialogComponent<null, null> = observer(function C
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('basicConnection_connectionDialog_newConnection')}
|
||||
subTitle={subtitle}
|
||||
icon={controller.dbDriver?.icon}
|
||||
@@ -83,7 +80,8 @@ export const ConnectionDialog: DialogComponent<null, null> = observer(function C
|
||||
onConnect={controller.onConnect}
|
||||
/>
|
||||
)}
|
||||
noBodyPadding
|
||||
noBodyPadding={controller.step === ConnectionStep.ConnectionTemplateSelect}
|
||||
fixedSize
|
||||
noOverflow
|
||||
onReject={rejectDialog}
|
||||
>
|
||||
|
||||
@@ -27,10 +27,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
PropertiesTable {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
@@ -74,6 +70,7 @@ export const ProcessorConfigureDialog = observer<Props>(function ProcessorConfig
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={title}
|
||||
footer={(
|
||||
<ProcessorConfigureDialogFooter
|
||||
@@ -83,6 +80,7 @@ export const ProcessorConfigureDialog = observer<Props>(function ProcessorConfig
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
fixedSize
|
||||
noOverflow
|
||||
noBodyPadding
|
||||
onReject={onClose}
|
||||
|
||||
@@ -19,10 +19,6 @@ import type { IExportContext } from '../IExportContext';
|
||||
import { ExportProcessorList } from './ExportProcessorList/ExportProcessorList';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
max-height: 500px;
|
||||
min-height: 500px;
|
||||
}
|
||||
ExportProcessorList {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -61,7 +57,9 @@ export const ProcessorSelectDialog = observer<Props>(function ProcessorSelectDia
|
||||
|
||||
return styled(styles)(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('data_transfer_dialog_title')}
|
||||
fixedSize
|
||||
noBodyPadding
|
||||
noOverflow
|
||||
onReject={onClose}
|
||||
|
||||
+1
-1
@@ -22,4 +22,4 @@ export interface ICellContext {
|
||||
editionState: DatabaseEditChangeType | null;
|
||||
}
|
||||
|
||||
export const CellContext = createContext<ICellContext | null>(null);
|
||||
export const CellContext = createContext<ICellContext>(undefined as any);
|
||||
|
||||
@@ -27,4 +27,4 @@ export interface IDataGridContext {
|
||||
getDataGridApi: () => DataGridHandle | null;
|
||||
}
|
||||
|
||||
export const DataGridContext = createContext<IDataGridContext | null>(null);
|
||||
export const DataGridContext = createContext<IDataGridContext>(undefined as any);
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ export class DataGridContextMenuCellEditingService {
|
||||
{
|
||||
id: 'row_revert',
|
||||
order: 7,
|
||||
icon: '/icons/data_revert.svg',
|
||||
icon: '/icons/data_revert_sm.svg',
|
||||
title: 'data_grid_table_editing_row_revert',
|
||||
isPresent(context) {
|
||||
return context.contextType === DataGridContextMenuService.cellContext;
|
||||
|
||||
+1
-3
@@ -17,9 +17,6 @@ import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { ClipboardService } from '@cloudbeaver/core-ui';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
min-width: 400px;
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -67,6 +64,7 @@ export const FilterCustomValueDialog: DialogComponent<IPayload, string | number>
|
||||
|
||||
return styled(styles)(
|
||||
<CommonDialogWrapper
|
||||
size='small'
|
||||
title={translate('data_grid_table_context_menu_filter_dialog_title')}
|
||||
footer={(
|
||||
<controls as="div">
|
||||
|
||||
+1
-1
@@ -26,4 +26,4 @@ export interface IDataGridSelectionContext {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const DataGridSelectionContext = createContext<IDataGridSelectionContext | null>(null);
|
||||
export const DataGridSelectionContext = createContext<IDataGridSelectionContext>(undefined as any);
|
||||
|
||||
+5
-9
@@ -11,7 +11,7 @@ import { useContext, useState } from 'react';
|
||||
import type { FormatterProps } from 'react-data-grid';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import type { IDataPresentationActions, IResultSetElementKey, IResultSetRowKey } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { EditingContext } from '../../Editing/EditingContext';
|
||||
@@ -44,19 +44,15 @@ export const CellFormatter = observer<Props>(function CellFormatter({ className,
|
||||
const cellContext = useContext(CellContext);
|
||||
const editingContext = useContext(EditingContext);
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const isEditing = cellContext?.isEditing ?? false;
|
||||
const showCellMenu = !isEditing && (
|
||||
const isEditing = cellContext.isEditing;
|
||||
const showCellMenu = getComputed(() => !isEditing && (
|
||||
rest.isCellSelected
|
||||
|| cellContext?.mouse.state.mouseEnter
|
||||
|| menuVisible
|
||||
);
|
||||
));
|
||||
|
||||
const spreadsheetActions = useObjectRef<IDataPresentationActions<IResultSetElementKey>>({
|
||||
edit(position) {
|
||||
if (!tableDataContext || !editingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = tableDataContext.getColumnIndexFromColumnKey(position.column);
|
||||
const rowIdx = tableDataContext.getRowIndexFromKey(position.row);
|
||||
|
||||
@@ -71,7 +67,7 @@ export const CellFormatter = observer<Props>(function CellFormatter({ className,
|
||||
<formatter-container>
|
||||
<CellFormatterFactory {...rest} isEditing={isEditing} />
|
||||
</formatter-container>
|
||||
{showCellMenu && context && cellContext?.cell && (
|
||||
{showCellMenu && cellContext.cell && (
|
||||
<menu-container>
|
||||
<CellMenu
|
||||
cellKey={cellContext.cell}
|
||||
|
||||
+1
-3
@@ -13,7 +13,6 @@ import type { FormatterProps } from 'react-data-grid';
|
||||
import { IResultSetRowKey, isBooleanValuePresentationAvailable } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { CellContext } from '../CellRenderer/CellContext';
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
import { TableDataContext } from '../TableDataContext';
|
||||
import { BooleanFormatter } from './CellFormatters/BooleanFormatter';
|
||||
import { TextFormatter } from './CellFormatters/TextFormatter';
|
||||
@@ -24,14 +23,13 @@ interface IProps extends FormatterProps<IResultSetRowKey> {
|
||||
|
||||
export const CellFormatterFactory = observer<IProps>(function CellFormatterFactory(props) {
|
||||
const formatterRef = useRef<React.FC<FormatterProps<IResultSetRowKey>> | null>(null);
|
||||
const context = useContext(DataGridContext);
|
||||
const tableDataContext = useContext(TableDataContext);
|
||||
const cellContext = useContext(CellContext);
|
||||
|
||||
if (!props.isEditing || formatterRef.current === null) {
|
||||
formatterRef.current = TextFormatter;
|
||||
|
||||
if (tableDataContext && context && cellContext?.cell) {
|
||||
if (cellContext.cell) {
|
||||
const resultColumn = tableDataContext.getColumnInfo(cellContext.cell.column);
|
||||
const value = tableDataContext.getCellValue(cellContext.cell);
|
||||
|
||||
|
||||
+4
-10
@@ -6,36 +6,30 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
|
||||
import { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import type { FormatterProps } from 'react-data-grid';
|
||||
|
||||
import { IconOrImage } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, IconOrImage } from '@cloudbeaver/core-blocks';
|
||||
import { isValidUrl } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { EditingContext } from '../../../Editing/EditingContext';
|
||||
import { CellEditor, IEditorRef } from '../../CellEditor/CellEditor';
|
||||
import { CellContext } from '../../CellRenderer/CellContext';
|
||||
import { DataGridContext } from '../../DataGridContext';
|
||||
import { TableDataContext } from '../../TableDataContext';
|
||||
|
||||
export const TextFormatter = observer<FormatterProps>(function TextFormatter({ row, column, isCellSelected }) {
|
||||
const editorRef = useRef<IEditorRef>(null);
|
||||
const context = useContext(DataGridContext);
|
||||
const editingContext = useContext(EditingContext);
|
||||
const tableDataContext = useContext(TableDataContext);
|
||||
const cellContext = useContext(CellContext);
|
||||
|
||||
if (!context || !tableDataContext || !editingContext || !cellContext?.cell) {
|
||||
if (!cellContext.cell) {
|
||||
throw new Error('Contexts required');
|
||||
}
|
||||
|
||||
const formatter = tableDataContext.format;
|
||||
const rawValue = useMemo(
|
||||
() => computed(() => formatter.get(tableDataContext.getCellValue(cellContext!.cell!)!)),
|
||||
[tableDataContext, cellContext.cell, formatter]
|
||||
).get();
|
||||
const rawValue = getComputed(() => formatter.get(tableDataContext.getCellValue(cellContext.cell!)!));
|
||||
|
||||
let classes = 'text-formatter';
|
||||
if (rawValue === null) {
|
||||
|
||||
+2
-6
@@ -11,7 +11,7 @@ import { useContext } from 'react';
|
||||
import type { HeaderRendererProps } from 'react-data-grid';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { StaticImage } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, StaticImage } from '@cloudbeaver/core-blocks';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
@@ -66,15 +66,11 @@ export const TableColumnHeader = observer<HeaderRendererProps<any>>(function Tab
|
||||
const tableDataContext = useContext(TableDataContext);
|
||||
const gridSelectionContext = useContext(DataGridSelectionContext);
|
||||
|
||||
if (!tableDataContext || !dataGridContext || !gridSelectionContext) {
|
||||
throw new Error('One of the following contexts are missed(data grid context, grid selection context, table data context)');
|
||||
}
|
||||
|
||||
const resultIndex = dataGridContext.resultIndex;
|
||||
const model = dataGridContext.model;
|
||||
let icon = calculatedColumn.icon;
|
||||
let columnName = calculatedColumn.name as string;
|
||||
const dataReadonly = tableDataContext.isReadOnly() || model.isReadonly();
|
||||
const dataReadonly = getComputed(() => tableDataContext.isReadOnly() || model.isReadonly());
|
||||
let columnReadOnly = !calculatedColumn.editable;
|
||||
let columnTooltip: string = columnName;
|
||||
|
||||
|
||||
@@ -51,4 +51,4 @@ export interface ITableData {
|
||||
isReadOnly: () => boolean;
|
||||
}
|
||||
|
||||
export const TableDataContext = createContext<ITableData | null>(null);
|
||||
export const TableDataContext = createContext<ITableData>(undefined as any);
|
||||
|
||||
@@ -22,4 +22,4 @@ export interface CellPosition {
|
||||
rowIdx: number;
|
||||
}
|
||||
|
||||
export const EditingContext = createContext<IEditingContext | null>(null);
|
||||
export const EditingContext = createContext<IEditingContext>(undefined as any);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.99951" y="11" width="12" height="2.5" rx="1" fill="#52C41A"/>
|
||||
<rect x="0.999512" y="3" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="0.999512" y="7" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="2.99902" y="11" width="12" height="2.5" rx="1" fill="#52C41A"/>
|
||||
<rect x="0.999023" y="3" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="0.999023" y="7" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
@@ -1,5 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.99951" y="6.75" width="12" height="2.5" rx="1" fill="#E73E52"/>
|
||||
<rect x="0.999512" y="3" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="0.999512" y="11" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="2.99902" y="6.75" width="12" height="2.5" rx="1" fill="#E73E52"/>
|
||||
<rect x="0.999023" y="3" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
<rect x="0.999023" y="11" width="12" height="2" rx="1" fill="#338ECC"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.11914 1H6.14884V2.0189H5.11914V1ZM14.3864 2.0189V1H13.3567V2.0189H14.3864ZM7.17854 1V2.0189H8.20824V1H7.17854ZM11.2973 1V2.0189H12.327V1H11.2973ZM14.3864 3.03779H13.3567V4.05669H14.3864V3.03779ZM5.11914 4.82086H6.14884V3.03779H5.11914V4.82086Z" fill="#338ECC"/>
|
||||
<path d="M10.2676 2.0189H9.23794V1H10.2676V2.0189Z" fill="#338ECC"/>
|
||||
<path d="M13.3567 6.09448V5.07559H14.3864V6.09448H13.3567Z" fill="#338ECC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 14V6H8V9.92857L10 10.3V5C10 4.44772 9.55229 4 9 4H2C1.44772 4 1 4.44771 1 5V15C1 15.5523 1.44772 16 2 16H9C9.55229 16 10 15.5523 10 15V13L8 12.8V14H3Z" fill="#338ECC"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3505 8.28909C13.5906 8.28909 13.9152 8.35353 14.1918 8.51951C14.4348 8.66529 14.604 8.87505 14.604 9.25591C14.604 9.60407 14.1939 10.3451 12.6385 10.7245C11.321 11.0458 9.32753 11.0439 6.60944 10.2855L7.38272 10.265L7.37602 8.99373L3.34082 9.14572L5.31914 12.4687L6.42679 11.8092L6.25799 11.5257C9.12647 12.3267 11.353 12.3649 12.9439 11.9769C14.6918 11.5505 15.8931 10.5191 15.8931 9.25591C15.8931 8.34767 15.4177 7.75175 14.855 7.41412C14.326 7.09669 13.755 7 13.3505 7V8.28909Z" fill="#338ECC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { makeObservable, observable } from 'mobx';
|
||||
import { action, makeObservable, observable } from 'mobx';
|
||||
|
||||
import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction';
|
||||
import type { IDatabaseDataActions } from './IDatabaseDataActions';
|
||||
@@ -19,13 +19,16 @@ export class DatabaseDataActions<TOptions, TResult extends IDatabaseDataResult>
|
||||
implements IDatabaseDataActions<TOptions, TResult> {
|
||||
private actions: Map<string, ActionsList<TOptions, TResult>>;
|
||||
private source: IDatabaseDataSource<TOptions, TResult>;
|
||||
private unCommittedActions: Map<string, ActionsList<TOptions, TResult>>;
|
||||
|
||||
constructor(source: IDatabaseDataSource<TOptions, TResult>) {
|
||||
this.actions = new Map();
|
||||
this.unCommittedActions = new Map();
|
||||
this.source = source;
|
||||
|
||||
makeObservable<DatabaseDataActions<TOptions, TResult>, 'actions'>(this, {
|
||||
actions: observable.shallow,
|
||||
updateResults: action,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,17 +51,12 @@ implements IDatabaseDataActions<TOptions, TResult> {
|
||||
throw new Error('DataFormat unsupported');
|
||||
}
|
||||
|
||||
if (!this.actions.has(result.id)) {
|
||||
this.actions.set(result.id, observable.array(undefined, { deep: false }));
|
||||
}
|
||||
|
||||
const actionsMap = this.actions.get(result.id)!;
|
||||
|
||||
let action = actionsMap.find(action => action instanceof Action);
|
||||
const actions = this.getOrCreateActionsList(result.id);
|
||||
let action = actions.find(action => action instanceof Action);
|
||||
|
||||
if (!action) {
|
||||
action = new Action(this.source, result);
|
||||
actionsMap.push(action);
|
||||
this.addActionToList(result.id, actions, action);
|
||||
}
|
||||
|
||||
return action as T;
|
||||
@@ -68,13 +66,8 @@ implements IDatabaseDataActions<TOptions, TResult> {
|
||||
result: TResult,
|
||||
Action: IDatabaseDataActionInterface<TOptions, TResult, T>
|
||||
): T | undefined {
|
||||
if (!this.actions.has(result.id)) {
|
||||
this.actions.set(result.id, observable.array(undefined, { deep: false }));
|
||||
}
|
||||
|
||||
const actionsMap = this.actions.get(result.id)!;
|
||||
|
||||
const action = actionsMap.find(action => action instanceof Action);
|
||||
const actions = this.getOrCreateActionsList(result.id);
|
||||
const action = actions.find(action => action instanceof Action);
|
||||
|
||||
return action as T | undefined;
|
||||
}
|
||||
@@ -97,4 +90,51 @@ implements IDatabaseDataActions<TOptions, TResult> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addActionToList(
|
||||
resultId: string,
|
||||
actions: ActionsList<TOptions, TResult>,
|
||||
action: IDatabaseDataAction<TOptions, TResult>
|
||||
) {
|
||||
actions = observable.array([...actions, action]);
|
||||
|
||||
this.unCommittedActions.set(resultId, actions);
|
||||
this.scheduleCommit(resultId);
|
||||
}
|
||||
|
||||
private getOrCreateActionsList(resultId: string): ActionsList<TOptions, TResult> {
|
||||
let actions = this.getActionsList(resultId);
|
||||
|
||||
if (!actions) {
|
||||
actions = this.createActionsList(resultId);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private createActionsList(resultId: string): ActionsList<TOptions, TResult> {
|
||||
const actions: ActionsList<TOptions, TResult> = observable.array(undefined, { deep: false });
|
||||
|
||||
this.unCommittedActions.set(resultId, actions);
|
||||
this.scheduleCommit(resultId);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private getActionsList(resultId: string): ActionsList<TOptions, TResult> | undefined {
|
||||
const committed = this.actions.get(resultId);
|
||||
const unCommitted = this.unCommittedActions.get(resultId);
|
||||
return unCommitted ?? committed;
|
||||
}
|
||||
|
||||
private scheduleCommit(resultId: string) {
|
||||
setTimeout(() => {
|
||||
const actions = this.unCommittedActions.get(resultId);
|
||||
|
||||
if (actions) {
|
||||
this.actions.set(resultId, actions);
|
||||
this.unCommittedActions.delete(resultId);
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, makeObservable } from 'mobx';
|
||||
import { observable, makeObservable, action } from 'mobx';
|
||||
|
||||
import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections';
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
@@ -76,6 +76,7 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
activeRequest: observable.ref,
|
||||
activeSave: observable.ref,
|
||||
activeTask: observable.ref,
|
||||
setSupportedDataFormats: action,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,6 @@ import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel
|
||||
import type { IDatabaseDataResult } from '../DatabaseDataModel/IDatabaseDataResult';
|
||||
|
||||
const styles = css`
|
||||
CommonDialogWrapper {
|
||||
min-height: 400px;
|
||||
min-width: 650px;
|
||||
}
|
||||
wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -94,6 +90,7 @@ export const ScriptPreviewDialog = observer<DialogComponentProps<Payload>>(funct
|
||||
|
||||
return styled(styles)(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('data_viewer_script_preview_dialog_title')}
|
||||
icon='sql-script'
|
||||
footer={(
|
||||
|
||||
@@ -18,9 +18,6 @@ import { ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { ThemeService, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
const productInfoDialogStyles = css`
|
||||
CommonDialogWrapper {
|
||||
min-width: 600px;
|
||||
}
|
||||
controls {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@@ -56,6 +53,7 @@ export const ProductInfoDialog = observer<DialogComponentProps<null>>(
|
||||
|
||||
return styled(styles)(
|
||||
<CommonDialogWrapper
|
||||
size='large'
|
||||
title={translate('app_product_info')}
|
||||
footer={(
|
||||
<controls as='div'>
|
||||
|
||||
Reference in New Issue
Block a user