mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
@@ -36,7 +36,10 @@
|
||||
anonymousAccessEnabled: true,
|
||||
authenticationEnabled: true,
|
||||
anonymousUserRole: "user",
|
||||
supportsCustomConnections: false
|
||||
supportsCustomConnections: false,
|
||||
|
||||
publicCredentialsSaveEnabled: true,
|
||||
adminCredentialsSaveEnabled: true
|
||||
|
||||
/*
|
||||
enabledDrivers: [
|
||||
|
||||
@@ -24,7 +24,8 @@ Require-Bundle: org.eclipse.core.runtime;visibility:=reexport,
|
||||
org.eclipse.jetty.util;visibility:=reexport,
|
||||
com.google.gson;visibility:=reexport,
|
||||
org.jkiss.bundle.graphql.java;visibility:=reexport,
|
||||
org.jkiss.bundle.apache.dbcp
|
||||
org.jkiss.bundle.apache.dbcp,
|
||||
org.jkiss.dbeaver.net.ssh
|
||||
Export-Package: io.cloudbeaver,
|
||||
io.cloudbeaver.auth.provider,
|
||||
io.cloudbeaver.auth.provider.local,
|
||||
|
||||
@@ -84,6 +84,10 @@ type ServerConfig {
|
||||
supportsCustomConnections: Boolean
|
||||
supportsConnectionBrowser: Boolean
|
||||
supportsWorkspaces: Boolean
|
||||
|
||||
publicCredentialsSaveEnabled: Boolean
|
||||
adminCredentialsSaveEnabled: Boolean
|
||||
|
||||
sessionExpireTime: Int
|
||||
localHostAddress: String
|
||||
|
||||
@@ -251,6 +255,12 @@ type ConnectionInfo {
|
||||
supportedDataFormats: [ ResultDataFormat! ]!
|
||||
}
|
||||
|
||||
type NetworkEndpointInfo {
|
||||
message: String
|
||||
clientVersion: String
|
||||
serverVersion: String
|
||||
}
|
||||
|
||||
type ObjectOrigin {
|
||||
type: ID!
|
||||
subType: ID
|
||||
@@ -409,6 +419,9 @@ extend type Mutation {
|
||||
# Test connection configuration. Returns remote server version
|
||||
testConnection( config: ConnectionConfig! ): ConnectionInfo!
|
||||
|
||||
# Test connection configuration. Returns remote server version
|
||||
testNetworkHandler( config: NetworkHandlerConfigInput! ): NetworkEndpointInfo!
|
||||
|
||||
# Initiate existing connection
|
||||
initConnection( id: ID!, credentials: Object, saveCredentials: Boolean ): ConnectionInfo!
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2020 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;
|
||||
|
||||
/**
|
||||
* WebNetworkEndpointInfo
|
||||
*/
|
||||
public class WebNetworkEndpointInfo {
|
||||
|
||||
private String message;
|
||||
private String clientVersion;
|
||||
private String serverVersion;
|
||||
|
||||
public WebNetworkEndpointInfo(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public WebNetworkEndpointInfo(String message, String clientVersion, String serverVersion) {
|
||||
this.message = message;
|
||||
this.clientVersion = clientVersion;
|
||||
this.serverVersion = serverVersion;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getClientVersion() {
|
||||
return clientVersion;
|
||||
}
|
||||
|
||||
public void setClientVersion(String clientVersion) {
|
||||
this.clientVersion = clientVersion;
|
||||
}
|
||||
|
||||
public String getServerVersion() {
|
||||
return serverVersion;
|
||||
}
|
||||
|
||||
public void setServerVersion(String serverVersion) {
|
||||
this.serverVersion = serverVersion;
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,16 @@ public class WebServerConfig {
|
||||
return application.getAppConfiguration().isSupportsUserWorkspaces();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isPublicCredentialsSaveEnabled() {
|
||||
return application.getAppConfiguration().isPublicCredentialsSaveEnabled();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isAdminCredentialsSaveEnabled() {
|
||||
return application.getAppConfiguration().isAdminCredentialsSaveEnabled();
|
||||
}
|
||||
|
||||
@Property
|
||||
public boolean isConfigurationMode() {
|
||||
return application.isConfigurationMode();
|
||||
|
||||
@@ -33,6 +33,8 @@ public class CBAppConfig {
|
||||
private boolean supportsCustomConnections = true;
|
||||
private boolean supportsConnectionBrowser = false;
|
||||
private boolean supportsUserWorkspaces = false;
|
||||
private boolean publicCredentialsSaveEnabled = true;
|
||||
private boolean adminCredentialsSaveEnabled = true;
|
||||
private String[] enabledDrivers = new String[0];
|
||||
private String[] disabledDrivers = new String[0];
|
||||
private String defaultAuthProvider = LocalAuthProvider.PROVIDER_ID;
|
||||
@@ -78,6 +80,22 @@ public class CBAppConfig {
|
||||
return supportsUserWorkspaces;
|
||||
}
|
||||
|
||||
public boolean isPublicCredentialsSaveEnabled() {
|
||||
return publicCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public void setPublicCredentialsSaveEnabled(boolean publicCredentialsSaveEnabled) {
|
||||
this.publicCredentialsSaveEnabled = publicCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public boolean isAdminCredentialsSaveEnabled() {
|
||||
return adminCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public void setAdminCredentialsSaveEnabled(boolean adminCredentialsSaveEnabled) {
|
||||
this.adminCredentialsSaveEnabled = adminCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public String[] getEnabledDrivers() {
|
||||
return enabledDrivers;
|
||||
}
|
||||
|
||||
@@ -573,6 +573,8 @@ public class CBApplication extends BaseApplicationImpl {
|
||||
JSONUtils.field(json, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
|
||||
JSONUtils.field(json, "authenticationEnabled", appConfig.isAuthenticationEnabled());
|
||||
JSONUtils.field(json, "supportsCustomConnections", appConfig.isSupportsCustomConnections());
|
||||
JSONUtils.field(json, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
|
||||
JSONUtils.field(json, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
|
||||
|
||||
if (!CommonUtils.isEmpty(appConfig.getPlugins())) {
|
||||
JSONUtils.serializeProperties(json, "plugins", appConfig.getPlugins());
|
||||
|
||||
+3
@@ -104,6 +104,9 @@ public interface DBWServiceCore extends DBWService {
|
||||
@WebAction
|
||||
WebConnectionInfo testConnection(WebSession webSession, WebConnectionConfig connectionConfig) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException;
|
||||
|
||||
@WebAction
|
||||
WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException;
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import graphql.schema.idl.TypeRuntimeWiring;
|
||||
import io.cloudbeaver.DBWebException;
|
||||
import io.cloudbeaver.WebServiceUtils;
|
||||
import io.cloudbeaver.model.WebConnectionConfig;
|
||||
import io.cloudbeaver.model.WebNetworkHandlerConfigInput;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.model.session.WebSessionManager;
|
||||
import io.cloudbeaver.server.CBPlatform;
|
||||
@@ -89,6 +90,7 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
|
||||
new WebConnectionConfig(env.getArgument("config"))))
|
||||
.dataFetcher("initConnection", env -> getService(env).initConnection(getWebSession(env), env.getArgument("id"), env.getArgument("credentials"), env.getArgument("saveCredentials")))
|
||||
.dataFetcher("testConnection", env -> getService(env).testConnection(getWebSession(env), getConnectionConfig(env)))
|
||||
.dataFetcher("testNetworkHandler", env -> getService(env).testNetworkHandler(getWebSession(env), new WebNetworkHandlerConfigInput(env.getArgument("config"))))
|
||||
.dataFetcher("closeConnection", env -> getService(env).closeConnection(getWebSession(env), env.getArgument("id")))
|
||||
.dataFetcher("deleteConnection", env -> getService(env).deleteConnection(getWebSession(env), env.getArgument("id")))
|
||||
|
||||
|
||||
+60
@@ -38,9 +38,16 @@ import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNDataSource;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNModel;
|
||||
import org.jkiss.dbeaver.model.navigator.DBNNode;
|
||||
import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration;
|
||||
import org.jkiss.dbeaver.model.net.DBWNetworkHandler;
|
||||
import org.jkiss.dbeaver.model.net.DBWTunnel;
|
||||
import org.jkiss.dbeaver.model.net.ssh.SSHImplementation;
|
||||
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
|
||||
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
|
||||
import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry;
|
||||
import org.jkiss.dbeaver.runtime.DBWorkbench;
|
||||
import org.jkiss.dbeaver.runtime.jobs.ConnectionTestJob;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
@@ -366,6 +373,59 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebNetworkEndpointInfo testNetworkHandler(WebSession webSession, WebNetworkHandlerConfigInput nhConfig) throws DBWebException {
|
||||
DBRProgressMonitor monitor = webSession.getProgressMonitor();
|
||||
monitor.beginTask("Instantiate SSH tunnel", 2);
|
||||
|
||||
NetworkHandlerDescriptor handlerDescriptor = NetworkHandlerRegistry.getInstance().getDescriptor(nhConfig.getId());
|
||||
if (handlerDescriptor == null) {
|
||||
throw new DBWebException("Network handler '" + nhConfig.getId() + "' not found");
|
||||
}
|
||||
try {
|
||||
DBWNetworkHandler handler = handlerDescriptor.createHandler(DBWNetworkHandler.class);
|
||||
if (handler instanceof DBWTunnel) {
|
||||
DBWTunnel tunnel = (DBWTunnel)handler;
|
||||
DBPConnectionConfiguration connectionConfig = new DBPConnectionConfiguration();
|
||||
connectionConfig.setHostName("localhost");
|
||||
connectionConfig.setHostPort(CommonUtils.toString(nhConfig.getProperties().get(DBWHandlerConfiguration.PROP_PORT)));
|
||||
try {
|
||||
monitor.subTask("Initialize tunnel");
|
||||
|
||||
DBWHandlerConfiguration configuration = new DBWHandlerConfiguration(handlerDescriptor, null);
|
||||
configuration.setUserName(nhConfig.getUserName());
|
||||
configuration.setPassword(nhConfig.getPassword());
|
||||
configuration.setSavePassword(true);
|
||||
configuration.setEnabled(true);
|
||||
configuration.setProperties(nhConfig.getProperties());
|
||||
tunnel.initializeHandler(monitor, DBWorkbench.getPlatform(), configuration, connectionConfig);
|
||||
monitor.worked(1);
|
||||
// Get info
|
||||
Object implementation = tunnel.getImplementation();
|
||||
if (implementation instanceof SSHImplementation) {
|
||||
return new WebNetworkEndpointInfo(
|
||||
"Connected",
|
||||
((SSHImplementation) implementation).getClientVersion(),
|
||||
((SSHImplementation) implementation).getServerVersion());
|
||||
} else {
|
||||
return new WebNetworkEndpointInfo("Connected");
|
||||
}
|
||||
} finally {
|
||||
monitor.subTask("Close tunnel");
|
||||
tunnel.closeTunnel(monitor);
|
||||
monitor.worked(1);
|
||||
}
|
||||
} else {
|
||||
return new WebNetworkEndpointInfo(nhConfig.getId() + " is not a tunnel");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DBWebException("Error testing network handler endpoint", e);
|
||||
} finally {
|
||||
// Close it
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebConnectionInfo closeConnection(WebSession webSession, String connectionId) throws DBWebException {
|
||||
return closeAndDeleteConnection(webSession, connectionId, false);
|
||||
|
||||
+7
-1
@@ -130,7 +130,13 @@ public class WebServiceNavigator implements DBWServiceNavigator {
|
||||
if (node == null) {
|
||||
throw new DBWebException("Navigator node '" + nodePath + "' not found");
|
||||
}
|
||||
node.refreshNode(monitor, this);
|
||||
if (node instanceof DBNDataSource) {
|
||||
// Do not refresh entire tree - just clear child nodes
|
||||
// Otherwise refresh may fail if navigator settings were changed.
|
||||
((DBNDataSource) node).cleanupNode();
|
||||
} else {
|
||||
node.refreshNode(monitor, this);
|
||||
}
|
||||
return true;
|
||||
} catch (DBException e) {
|
||||
throw new DBWebException("Error refreshing navigator node '" + nodePath + "'", e);
|
||||
|
||||
@@ -54,6 +54,8 @@ input ServerConfigInput {
|
||||
anonymousAccessEnabled: Boolean
|
||||
authenticationEnabled: Boolean
|
||||
customConnectionsEnabled: Boolean
|
||||
publicCredentialsSaveEnabled: Boolean
|
||||
adminCredentialsSaveEnabled: Boolean
|
||||
|
||||
sessionExpireTime: Int
|
||||
}
|
||||
|
||||
+10
-10
@@ -35,6 +35,8 @@ public class AdminServerConfig {
|
||||
private boolean anonymousAccessEnabled;
|
||||
private boolean authenticationEnabled;
|
||||
private boolean customConnectionsEnabled;
|
||||
private boolean publicCredentialsSaveEnabled;
|
||||
private boolean adminCredentialsSaveEnabled;
|
||||
|
||||
private long sessionExpireTime;
|
||||
|
||||
@@ -47,6 +49,8 @@ public class AdminServerConfig {
|
||||
this.anonymousAccessEnabled = JSONUtils.getBoolean(params, "anonymousAccessEnabled", appConfig.isAnonymousAccessEnabled());
|
||||
this.authenticationEnabled = JSONUtils.getBoolean(params, "authenticationEnabled", appConfig.isAuthenticationEnabled());
|
||||
this.customConnectionsEnabled = JSONUtils.getBoolean(params, "customConnectionsEnabled", appConfig.isSupportsCustomConnections());
|
||||
this.publicCredentialsSaveEnabled = JSONUtils.getBoolean(params, "publicCredentialsSaveEnabled", appConfig.isPublicCredentialsSaveEnabled());
|
||||
this.adminCredentialsSaveEnabled = JSONUtils.getBoolean(params, "adminCredentialsSaveEnabled", appConfig.isAdminCredentialsSaveEnabled());
|
||||
|
||||
this.sessionExpireTime = JSONUtils.getLong(params, "sessionExpireTime", -1);
|
||||
}
|
||||
@@ -79,24 +83,20 @@ public class AdminServerConfig {
|
||||
return anonymousAccessEnabled;
|
||||
}
|
||||
|
||||
public void setAnonymousAccessEnabled(boolean anonymousAccessEnabled) {
|
||||
this.anonymousAccessEnabled = anonymousAccessEnabled;
|
||||
}
|
||||
|
||||
public boolean isAuthenticationEnabled() {
|
||||
return authenticationEnabled;
|
||||
}
|
||||
|
||||
public void setAuthenticationEnabled(boolean authenticationEnabled) {
|
||||
this.authenticationEnabled = authenticationEnabled;
|
||||
}
|
||||
|
||||
public boolean isCustomConnectionsEnabled() {
|
||||
return customConnectionsEnabled;
|
||||
}
|
||||
|
||||
public void setCustomConnectionsEnabled(boolean customConnectionsEnabled) {
|
||||
this.customConnectionsEnabled = customConnectionsEnabled;
|
||||
public boolean isPublicCredentialsSaveEnabled() {
|
||||
return publicCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public boolean isAdminCredentialsSaveEnabled() {
|
||||
return adminCredentialsSaveEnabled;
|
||||
}
|
||||
|
||||
public long getSessionExpireTime() {
|
||||
|
||||
+2
@@ -350,6 +350,8 @@ public class WebServiceAdmin implements DBWServiceAdmin {
|
||||
appConfig.setAnonymousAccessEnabled(config.isAnonymousAccessEnabled());
|
||||
appConfig.setAuthenticationEnabled(config.isAuthenticationEnabled());
|
||||
appConfig.setSupportsCustomConnections(config.isCustomConnectionsEnabled());
|
||||
appConfig.setPublicCredentialsSaveEnabled(config.isPublicCredentialsSaveEnabled());
|
||||
appConfig.setAdminCredentialsSaveEnabled(config.isAdminCredentialsSaveEnabled());
|
||||
String adminName = config.getAdminName();
|
||||
String adminPassword = config.getAdminPassword();
|
||||
if (CommonUtils.isEmpty(adminName)) {
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
<artifactId>derbyshared</artifactId>
|
||||
<version>10.15.1.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.derby</groupId>
|
||||
<artifactId>derbyclient</artifactId>
|
||||
<version>10.15.1.3</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -53,8 +53,8 @@ export const InlineEditorStyles = composes(
|
||||
letter-spacing: inherit;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding-left: 11px; /* because of left border */
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
padding-top: 1px; /* because of -1px top */
|
||||
line-height: 24px;
|
||||
width: 100%;
|
||||
|
||||
@@ -89,7 +89,15 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
|
||||
return null;
|
||||
}
|
||||
|
||||
let checked = state ? state[name] : checkedControlled;
|
||||
let checked = checkedControlled;
|
||||
|
||||
if (state) {
|
||||
if (typeof state[name] === 'string') {
|
||||
checked = state[name] === 'true';
|
||||
} else {
|
||||
checked = state[name];
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(checked)) {
|
||||
checked = checked.includes(value);
|
||||
|
||||
@@ -78,10 +78,12 @@ interface ICheckboxMarkupProps extends React.InputHTMLAttributes<HTMLInputElemen
|
||||
indeterminate?: boolean;
|
||||
}
|
||||
|
||||
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({ label, className, ...rest }) {
|
||||
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
|
||||
label, className, title, ...rest
|
||||
}) {
|
||||
return styled(useStyles(checkboxStyles, checkboxMod.primary, rest.disabled
|
||||
&& checkboxState.disabled, rest.checked && checkboxState.checked))(
|
||||
<checkbox-container className={className} as='div'>
|
||||
<checkbox-container className={className} title={title} as='div'>
|
||||
<checkbox as='div'>
|
||||
<checkbox-input as='input' type='checkbox' {...rest} />
|
||||
<checkbox-background as='div'>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { InputField } from '@cloudbeaver/core-blocks';
|
||||
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { FieldCheckbox } from '../FormControls/Checkboxes/FieldCheckbox';
|
||||
import { FormFieldDescription } from '../FormControls/FormFieldDescription';
|
||||
import { FormGroup } from '../FormControls/FormGroup';
|
||||
import { isControlPresented } from '../FormControls/isControlPresented';
|
||||
@@ -48,6 +49,7 @@ const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
|
||||
}) {
|
||||
const href = property.features.includes('href');
|
||||
const password = property.features.includes('password');
|
||||
const checkbox = property.dataType === 'Boolean';
|
||||
let description: string | undefined;
|
||||
|
||||
if (href) {
|
||||
@@ -73,6 +75,19 @@ const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
|
||||
description = 'Password saved';
|
||||
}
|
||||
|
||||
if (checkbox) {
|
||||
return (
|
||||
<FieldCheckbox
|
||||
name={property.id!}
|
||||
state={state}
|
||||
checkboxLabel={property.displayName}
|
||||
title={property.description}
|
||||
disabled={disabled}
|
||||
mod='surface'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputField
|
||||
type={password ? 'password' : 'text'}
|
||||
@@ -93,7 +108,7 @@ const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
|
||||
|
||||
interface ObjectPropertyFormProps {
|
||||
properties: ObjectPropertyInfo[] | undefined;
|
||||
credentials: Record<string, string | number>;
|
||||
state: Record<string, string | number>;
|
||||
editable?: boolean;
|
||||
autofillToken?: string;
|
||||
className?: string;
|
||||
@@ -106,7 +121,7 @@ interface ObjectPropertyFormProps {
|
||||
|
||||
export const ObjectPropertyInfoForm: React.FC<ObjectPropertyFormProps> = observer(function ObjectPropertyInfoForm({
|
||||
properties,
|
||||
credentials,
|
||||
state,
|
||||
editable = true,
|
||||
autofillToken = '',
|
||||
className,
|
||||
@@ -134,7 +149,7 @@ export const ObjectPropertyInfoForm: React.FC<ObjectPropertyFormProps> = observe
|
||||
<FormGroup key={property.id}>
|
||||
<RenderField
|
||||
property={property}
|
||||
state={credentials}
|
||||
state={state}
|
||||
editable={editable}
|
||||
autofillToken={autofillToken}
|
||||
disabled={disabled}
|
||||
|
||||
+10
-3
@@ -26,7 +26,7 @@ interface IValidationStatus {
|
||||
}
|
||||
@injectable()
|
||||
export class ConnectionFormController
|
||||
implements IInitializableController {
|
||||
implements IInitializableController {
|
||||
isSaving: boolean;
|
||||
|
||||
readonly afterSave: IExecutor<string>;
|
||||
@@ -80,7 +80,10 @@ implements IInitializableController {
|
||||
const connectionConfig = this.getConnectionConfig();
|
||||
const validation = this.validate(connectionConfig);
|
||||
if (!validation.status) {
|
||||
this.notificationService.logError({ title: this.model.editing ? 'connections_administration_connection_save_error' : 'connections_administration_connection_create_error', message: validation.errorMessage });
|
||||
this.notificationService.logError({
|
||||
title: this.model.editing ? 'connections_administration_connection_save_error' : 'connections_administration_connection_create_error',
|
||||
message: validation.errorMessage,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ implements IInitializableController {
|
||||
if (!config.name?.length) {
|
||||
validationStatus.errorMessage = "Field 'name' can't be empty";
|
||||
} else if (this.model.editing && this.isConnectionNameAlreadyExists(config.name)) {
|
||||
validationStatus.errorMessage = 'Connection with this name already exists';
|
||||
validationStatus.errorMessage = `Connection with ${config.name} name already exists`;
|
||||
}
|
||||
|
||||
validationStatus.status = !validationStatus.errorMessage;
|
||||
@@ -173,6 +176,10 @@ implements IInitializableController {
|
||||
}
|
||||
config.networkHandlersConfig = this.model.networkHandlersState;
|
||||
|
||||
if (Object.keys(this.model.connection.providerProperties).length > 0) {
|
||||
config.providerProperties = this.model.connection.providerProperties;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -140,7 +140,7 @@ export const Options = observer(function Options({
|
||||
<ObjectPropertyInfoForm
|
||||
autofillToken='new-password'
|
||||
properties={controller.properties}
|
||||
credentials={model.credentials}
|
||||
state={model.credentials}
|
||||
disabled={disabled}
|
||||
showRememberTip
|
||||
/>
|
||||
@@ -157,6 +157,18 @@ export const Options = observer(function Options({
|
||||
</>
|
||||
)}
|
||||
</FormBoxElement>
|
||||
{controller.providerProperties && controller.providerProperties.length > 0 && (
|
||||
<FormBoxElement>
|
||||
<FormGroup>
|
||||
<InputGroup>{translate('connections_connection_edit_settings')}</InputGroup>
|
||||
</FormGroup>
|
||||
<ObjectPropertyInfoForm
|
||||
properties={controller.providerProperties}
|
||||
state={model.connection.providerProperties}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</FormBoxElement>
|
||||
)}
|
||||
</FormBox>
|
||||
</SubmittingForm>
|
||||
);
|
||||
|
||||
+6
-1
@@ -18,7 +18,7 @@ import type { IConnectionFormModel } from '../IConnectionFormModel';
|
||||
|
||||
@injectable()
|
||||
export class OptionsController
|
||||
implements IInitializableController {
|
||||
implements IInitializableController {
|
||||
get drivers(): DBDriver[] {
|
||||
return Array.from(this.dbDriverResource.data.values())
|
||||
.filter(({ id }) => this.model.availableDrivers.includes(id));
|
||||
@@ -46,6 +46,10 @@ implements IInitializableController {
|
||||
return this.authModel?.properties;
|
||||
}
|
||||
|
||||
get providerProperties(): ObjectPropertyInfo[] | undefined {
|
||||
return this.dbDriverResource.get(this.model.connection.driverId)?.providerProperties;
|
||||
}
|
||||
|
||||
private model!: IConnectionFormModel;
|
||||
/** we want to save prev generated connection name to detect if user changed "name" field to turn off autofill */
|
||||
private prevGeneratedName: string | null = null;
|
||||
@@ -87,6 +91,7 @@ implements IInitializableController {
|
||||
this.setDefaultParameters(prevDriverId);
|
||||
this.cleanCredentials();
|
||||
this.model.connection.properties = {};
|
||||
this.model.connection.providerProperties = {};
|
||||
this.model.connection.authModel = this.driver?.defaultAuthModel;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { TextPlaceholder, useTab, ObjectPropertyInfoForm, FormBox, FormBoxElement, FormGroup, InputGroup, Loader, useTabState, ExceptionMessage } from '@cloudbeaver/core-blocks';
|
||||
import { TextPlaceholder, useTab, ObjectPropertyInfoForm, FormBox, FormBoxElement, FormGroup, Loader, useTabState, ExceptionMessage } from '@cloudbeaver/core-blocks';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
@@ -94,7 +94,7 @@ export const OriginInfo: TabContainerPanelComponent<IConnectionFormProps> = obse
|
||||
<FormGroup><br /></FormGroup>
|
||||
<ObjectPropertyInfoForm
|
||||
properties={state.properties}
|
||||
credentials={state.state}
|
||||
state={state.state}
|
||||
editable={false}
|
||||
autoHide
|
||||
/>
|
||||
|
||||
@@ -71,6 +71,7 @@ export class ConnectionsResource extends CachedMapResource<string, AdminConnecti
|
||||
useUrl: false,
|
||||
authProperties: [],
|
||||
properties: {},
|
||||
providerProperties: {},
|
||||
origin: {
|
||||
type: 'local',
|
||||
displayName: 'Local',
|
||||
|
||||
@@ -15,21 +15,22 @@ import {
|
||||
import { MetadataMap } from '@cloudbeaver/core-utils';
|
||||
|
||||
export type DBDriver = Pick<
|
||||
DriverInfo,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'icon'
|
||||
| 'description'
|
||||
| 'defaultPort'
|
||||
| 'defaultDatabase'
|
||||
| 'defaultServer'
|
||||
| 'defaultUser'
|
||||
| 'sampleURL'
|
||||
| 'embedded'
|
||||
| 'anonymousAccess'
|
||||
| 'promotedScore'
|
||||
| 'defaultAuthModel'
|
||||
| 'applicableNetworkHandlers'
|
||||
DriverInfo,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'icon'
|
||||
| 'description'
|
||||
| 'defaultPort'
|
||||
| 'defaultDatabase'
|
||||
| 'defaultServer'
|
||||
| 'defaultUser'
|
||||
| 'sampleURL'
|
||||
| 'embedded'
|
||||
| 'anonymousAccess'
|
||||
| 'promotedScore'
|
||||
| 'defaultAuthModel'
|
||||
| 'applicableNetworkHandlers'
|
||||
| 'providerProperties'
|
||||
>;
|
||||
|
||||
@injectable()
|
||||
|
||||
@@ -101,7 +101,7 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
|
||||
<ObjectPropertyInfoForm
|
||||
autofillToken={`section-${connection.connectionInfo?.id || ''} section-auth`}
|
||||
properties={connection.connectionInfo?.authProperties}
|
||||
credentials={controller.config.credentials}
|
||||
state={controller.config.credentials}
|
||||
disabled={controller.isAuthenticating}
|
||||
/>
|
||||
<FormGroup>
|
||||
|
||||
@@ -16,6 +16,7 @@ export default [
|
||||
['connections_connection_create_search_database', 'Search'],
|
||||
['connections_connection_edit_save_credentials', 'Save credentials'],
|
||||
['connections_connection_edit_authentication', 'Authentication'],
|
||||
['connections_connection_edit_settings', 'Settings'],
|
||||
['connections_connection_edit_access', 'Access'],
|
||||
['connections_connection_edit_access_load_failed', 'Fail to get connection access'],
|
||||
['connections_connection_edit_access_role', 'Role'],
|
||||
|
||||
@@ -15,6 +15,7 @@ export default [
|
||||
['connections_connection_create_search_database', 'Найти'],
|
||||
['connections_connection_edit_save_credentials', ' Запомнить данные'],
|
||||
['connections_connection_edit_authentication', 'Авторизация'],
|
||||
['connections_connection_edit_settings', 'Настройки'],
|
||||
['connections_connection_edit_access', 'Доступ'],
|
||||
['connections_connection_edit_access_load_failed', 'Не удалось загрузить информацию доступа'],
|
||||
['connections_connection_edit_access_role', 'Роль'],
|
||||
|
||||
@@ -15,6 +15,18 @@ query driverList {
|
||||
|
||||
defaultAuthModel
|
||||
# applicableAuthModel
|
||||
|
||||
providerProperties {
|
||||
id
|
||||
displayName
|
||||
description
|
||||
category
|
||||
dataType
|
||||
defaultValue
|
||||
validValues
|
||||
features
|
||||
order
|
||||
}
|
||||
applicableNetworkHandlers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ fragment AdminConnection on ConnectionInfo {
|
||||
databaseName
|
||||
url
|
||||
properties
|
||||
providerProperties
|
||||
|
||||
features
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@ export interface Mutation {
|
||||
sqlContextSetDefaults: Scalars['Boolean'];
|
||||
sqlResultClose: Scalars['Boolean'];
|
||||
testConnection: ConnectionInfo;
|
||||
testNetworkHandler: NetworkEndpointInfo;
|
||||
touchSession?: Maybe<Scalars['Boolean']>;
|
||||
updateResultsData?: Maybe<SqlExecuteInfo>;
|
||||
updateResultsDataBatch?: Maybe<SqlExecuteInfo>;
|
||||
@@ -374,6 +375,10 @@ export interface MutationTestConnectionArgs {
|
||||
config: ConnectionConfig;
|
||||
}
|
||||
|
||||
export interface MutationTestNetworkHandlerArgs {
|
||||
config: NetworkHandlerConfigInput;
|
||||
}
|
||||
|
||||
export interface MutationUpdateResultsDataArgs {
|
||||
connectionId: Scalars['ID'];
|
||||
contextId: Scalars['ID'];
|
||||
@@ -448,6 +453,8 @@ export interface ServerConfig {
|
||||
supportsCustomConnections?: Maybe<Scalars['Boolean']>;
|
||||
supportsConnectionBrowser?: Maybe<Scalars['Boolean']>;
|
||||
supportsWorkspaces?: Maybe<Scalars['Boolean']>;
|
||||
publicCredentialsSaveEnabled?: Maybe<Scalars['Boolean']>;
|
||||
adminCredentialsSaveEnabled?: Maybe<Scalars['Boolean']>;
|
||||
sessionExpireTime?: Maybe<Scalars['Int']>;
|
||||
localHostAddress?: Maybe<Scalars['String']>;
|
||||
configurationMode?: Maybe<Scalars['Boolean']>;
|
||||
@@ -569,6 +576,12 @@ export interface ConnectionInfo {
|
||||
supportedDataFormats: ResultDataFormat[];
|
||||
}
|
||||
|
||||
export interface NetworkEndpointInfo {
|
||||
message?: Maybe<Scalars['String']>;
|
||||
clientVersion?: Maybe<Scalars['String']>;
|
||||
serverVersion?: Maybe<Scalars['String']>;
|
||||
}
|
||||
|
||||
export interface ObjectOrigin {
|
||||
type: Scalars['ID'];
|
||||
subType?: Maybe<Scalars['ID']>;
|
||||
@@ -844,6 +857,8 @@ export interface ServerConfigInput {
|
||||
anonymousAccessEnabled?: Maybe<Scalars['Boolean']>;
|
||||
authenticationEnabled?: Maybe<Scalars['Boolean']>;
|
||||
customConnectionsEnabled?: Maybe<Scalars['Boolean']>;
|
||||
publicCredentialsSaveEnabled?: Maybe<Scalars['Boolean']>;
|
||||
adminCredentialsSaveEnabled?: Maybe<Scalars['Boolean']>;
|
||||
sessionExpireTime?: Maybe<Scalars['Int']>;
|
||||
}
|
||||
|
||||
@@ -1097,7 +1112,12 @@ export type DeleteConnectionMutation = Pick<Mutation, 'deleteConnection'>;
|
||||
|
||||
export type DriverListQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export interface DriverListQuery { driverList: Array<Pick<DriverInfo, 'id' | 'name' | 'icon' | 'description' | 'defaultPort' | 'defaultDatabase' | 'defaultServer' | 'defaultUser' | 'sampleURL' | 'embedded' | 'anonymousAccess' | 'promotedScore' | 'defaultAuthModel' | 'applicableNetworkHandlers'>> }
|
||||
export interface DriverListQuery {
|
||||
driverList: Array<(
|
||||
Pick<DriverInfo, 'id' | 'name' | 'icon' | 'description' | 'defaultPort' | 'defaultDatabase' | 'defaultServer' | 'defaultUser' | 'sampleURL' | 'embedded' | 'anonymousAccess' | 'promotedScore' | 'defaultAuthModel' | 'applicableNetworkHandlers'>
|
||||
& { providerProperties: Array<Pick<ObjectPropertyInfo, 'id' | 'displayName' | 'description' | 'category' | 'dataType' | 'defaultValue' | 'validValues' | 'features' | 'order'>> }
|
||||
)>;
|
||||
}
|
||||
|
||||
export type DriverPropertiesQueryVariables = Exact<{
|
||||
driverId: Scalars['ID'];
|
||||
@@ -1216,7 +1236,7 @@ export type NavGetStructContainersQueryVariables = Exact<{
|
||||
export interface NavGetStructContainersQuery { navGetStructContainers: { catalogList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>>; schemaList: Array<Pick<DatabaseObjectInfo, 'name' | 'description' | 'type' | 'features'>> } }
|
||||
|
||||
export type AdminConnectionFragment = (
|
||||
Pick<ConnectionInfo, 'id' | 'name' | 'description' | 'driverId' | 'template' | 'connected' | 'useUrl' | 'readOnly' | 'saveCredentials' | 'host' | 'port' | 'databaseName' | 'url' | 'properties' | 'features' | 'authNeeded' | 'authModel' | 'supportedDataFormats'>
|
||||
Pick<ConnectionInfo, 'id' | 'name' | 'description' | 'driverId' | 'template' | 'connected' | 'useUrl' | 'readOnly' | 'saveCredentials' | 'host' | 'port' | 'databaseName' | 'url' | 'properties' | 'providerProperties' | 'features' | 'authNeeded' | 'authModel' | 'supportedDataFormats'>
|
||||
& { origin: ObjectOriginInfoFragment; authProperties: UserConnectionAuthPropertiesFragment[]; networkHandlersConfig: Array<Pick<NetworkHandlerConfig, 'id' | 'enabled' | 'userName' | 'password' | 'savePassword' | 'properties'>> }
|
||||
);
|
||||
|
||||
@@ -1540,6 +1560,7 @@ export const AdminConnectionFragmentDoc = `
|
||||
databaseName
|
||||
url
|
||||
properties
|
||||
providerProperties
|
||||
features
|
||||
origin {
|
||||
...ObjectOriginInfo
|
||||
@@ -1907,6 +1928,17 @@ export const DriverListDocument = `
|
||||
anonymousAccess
|
||||
promotedScore
|
||||
defaultAuthModel
|
||||
providerProperties {
|
||||
id
|
||||
displayName
|
||||
description
|
||||
category
|
||||
dataType
|
||||
defaultValue
|
||||
validValues
|
||||
features
|
||||
order
|
||||
}
|
||||
applicableNetworkHandlers
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ export const OriginInfo: TabContainerPanelComponent<IUserFormProps> = observer(f
|
||||
<FormGroup><br /></FormGroup>
|
||||
<ObjectPropertyInfoForm
|
||||
properties={state.properties}
|
||||
credentials={state.state}
|
||||
state={state.state}
|
||||
editable={false}
|
||||
autoHide
|
||||
/>
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ export const ConnectionForm = observer(function ConnectionForm({
|
||||
<ObjectPropertyInfoForm
|
||||
autofillToken='new-password'
|
||||
properties={controller.authModel.properties}
|
||||
credentials={controller.config.credentials}
|
||||
state={controller.config.credentials}
|
||||
disabled={controller.isConnecting}
|
||||
/>
|
||||
<FormGroup>
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export const ConnectionDialog = observer(function ConnectionDialog({
|
||||
<ObjectPropertyInfoForm
|
||||
autofillToken={`section-${controller.template?.id || ''} section-auth`}
|
||||
properties={controller.authModel.properties}
|
||||
credentials={controller.credentials}
|
||||
state={controller.credentials}
|
||||
disabled={controller.isConnecting}
|
||||
/>
|
||||
</SubmittingForm>
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-data-grid": "^7.0.0-canary.30"
|
||||
"react-data-grid": "^7.0.0-canary.30",
|
||||
"react-popper": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudbeaver/plugin-data-viewer": "^0.1.0",
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useLayoutEffect, useState } from 'react';
|
||||
import type { EditorProps } from 'react-data-grid';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { usePopper } from 'react-popper';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { InlineEditor, InlineEditorControls } from '@cloudbeaver/core-app';
|
||||
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
|
||||
const styles = css`
|
||||
editor {
|
||||
composes: theme-typography--body2 from global;
|
||||
}
|
||||
InlineEditor {
|
||||
font-size: 12px;
|
||||
left: -1px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CellEditor: React.FC<EditorProps<any, any>> = observer(function CellEditor({
|
||||
rowIdx,
|
||||
row,
|
||||
column,
|
||||
editorPortalTarget,
|
||||
onClose,
|
||||
}) {
|
||||
const [elementRef, setElementRef] = useState<HTMLDivElement | null>(null);
|
||||
const [popperRef, setPopperRef] = useState<HTMLDivElement | null>(null);
|
||||
const popper = usePopper(elementRef, popperRef, {
|
||||
placement: 'right',
|
||||
});
|
||||
const context = useContext(DataGridContext);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (elementRef && popperRef) {
|
||||
const size = elementRef.parentElement?.getBoundingClientRect();
|
||||
if (size) {
|
||||
popperRef.style.width = (size.width + 2) + 'px';
|
||||
popperRef.style.height = (size.height + 2) + 'px';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!context) {
|
||||
throw new Error('DataGridContext should be provided');
|
||||
}
|
||||
const modelResultData = context.model?.getResult(context.resultIndex);
|
||||
|
||||
const value = row[column.key];
|
||||
|
||||
const isLastColumn = column.isLastFrozenColumn; // TODO: do not work
|
||||
const isLastRow = rowIdx > 0 && ((modelResultData?.data.rows.length || 0) - 1 === rowIdx);
|
||||
let controlsPosition: InlineEditorControls = 'right';
|
||||
|
||||
if (isLastColumn) {
|
||||
controlsPosition = isLastRow ? 'top' : 'bottom';
|
||||
}
|
||||
|
||||
const handleSave = () => onClose(false);
|
||||
const handleReject = () => onClose(false);
|
||||
const handleChange = () => {};
|
||||
const handleUndo = () => onClose(false);
|
||||
|
||||
return styled(styles)(
|
||||
<box ref={setElementRef} as='div'>
|
||||
{createPortal((
|
||||
<editor ref={setPopperRef} as="div" style={popper.styles.popper} {...popper.attributes.popper}>
|
||||
<InlineEditor
|
||||
type={typeof value === 'number' ? 'number' : 'text'}
|
||||
value={value}
|
||||
controlsPosition={controlsPosition}
|
||||
edited
|
||||
hideSave
|
||||
hideCancel
|
||||
autofocus
|
||||
onSave={handleSave}
|
||||
onReject={handleReject}
|
||||
onChange={handleChange}
|
||||
onUndo={handleUndo}
|
||||
/>
|
||||
</editor>
|
||||
), context.getEditorPortal() || editorPortalTarget)}
|
||||
</box>
|
||||
);
|
||||
});
|
||||
@@ -10,9 +10,10 @@ import { createContext } from 'react';
|
||||
|
||||
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
interface IDataGridContext {
|
||||
model: IDatabaseDataModel<any, any>;
|
||||
export interface IDataGridContext {
|
||||
model: IDatabaseDataModel<any>;
|
||||
resultIndex: number;
|
||||
getEditorPortal: () => HTMLDivElement | null;
|
||||
}
|
||||
|
||||
export const DataGridContext = createContext<IDataGridContext | null>(null);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import DataGrid from 'react-data-grid';
|
||||
import type { Column } from 'react-data-grid';
|
||||
import styled from 'reshadow';
|
||||
@@ -21,7 +21,8 @@ import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { ResultSetTools } from '../ResultSetTools';
|
||||
import baseStyles from '../styles/base.scss';
|
||||
import { reactGridStyles } from '../styles/styles';
|
||||
import { DataGridContext } from './DataGridContext';
|
||||
import { CellEditor } from './CellEditor/CellEditor';
|
||||
import { DataGridContext, IDataGridContext } from './DataGridContext';
|
||||
import { DataGridSelectionContext } from './DataGridSelection/DataGridSelectionContext';
|
||||
import { useGridSelectionContext } from './DataGridSelection/useGridSelectionContext';
|
||||
import { DataGridSortingContext } from './DataGridSorting/DataGridSortingContext';
|
||||
@@ -30,7 +31,7 @@ import { DataGridTableContainer } from './DataGridTableContainer';
|
||||
import { CellFormatter } from './Formatters/CellFormatter';
|
||||
import { IndexFormatter } from './Formatters/IndexFormatter';
|
||||
import { RowRenderer } from './RowRenderer/RowRenderer';
|
||||
import { TableColumnHeaderNew } from './TableColumnHeader/TableColumnHeader-new';
|
||||
import { TableColumnHeader } from './TableColumnHeader/TableColumnHeader';
|
||||
|
||||
interface Props {
|
||||
model: IDatabaseDataModel<any>;
|
||||
@@ -54,6 +55,7 @@ const indexColumn: Column<any[], any> = {
|
||||
};
|
||||
|
||||
export const DataGridTable: React.FC<Props> = observer(function DataGridTable({ model, resultIndex, className }) {
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const styles = useStyles(reactGridStyles, baseStyles);
|
||||
|
||||
const modelResultData = model?.getResult(resultIndex);
|
||||
@@ -114,16 +116,36 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
width: Math.min(300, measuredCells[i]),
|
||||
minWidth: 40,
|
||||
resizable: true,
|
||||
headerRenderer: TableColumnHeaderNew,
|
||||
editable: true,
|
||||
headerRenderer: TableColumnHeader,
|
||||
formatter: CellFormatter,
|
||||
editor: CellEditor,
|
||||
editorOptions: {
|
||||
onCellKeyDown: event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
onNavigation: event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}));
|
||||
columns.unshift(indexColumn);
|
||||
|
||||
return { rows, columns };
|
||||
}), [modelResultData]).get();
|
||||
|
||||
const gridContext = useMemo<IDataGridContext>(() => ({
|
||||
model,
|
||||
resultIndex,
|
||||
getEditorPortal: () => editorRef.current,
|
||||
}), [model, resultIndex, editorRef]);
|
||||
|
||||
return styled(styles)(
|
||||
<DataGridContext.Provider value={{ model, resultIndex }}>
|
||||
<DataGridContext.Provider value={gridContext}>
|
||||
<DataGridSortingContext.Provider value={gridSortingContext}>
|
||||
<DataGridSelectionContext.Provider value={gridSelectionContext}>
|
||||
<DataGridTableContainer modelResultData={modelResultData}>
|
||||
@@ -136,6 +158,7 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
rowRenderer={RowRenderer}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
<div ref={editorRef} />
|
||||
</DataGridTableContainer>
|
||||
</DataGridSelectionContext.Provider>
|
||||
</DataGridSortingContext.Provider>
|
||||
|
||||
+3
-4
@@ -6,9 +6,8 @@
|
||||
* 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, useMemo } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import type { HeaderRendererProps } from 'react-data-grid';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
@@ -79,7 +78,7 @@ function getColumn(colIdx: number, source: SqlResultSet) {
|
||||
return source.columns?.[colIdx];
|
||||
}
|
||||
|
||||
export const TableColumnHeaderNew: React.FC<HeaderRendererProps<any>> = observer(function TableColumnHeaderNew({
|
||||
export const TableColumnHeader: React.FC<HeaderRendererProps<any>> = observer(function TableColumnHeader({
|
||||
column: calculatedColumn,
|
||||
}) {
|
||||
const dataGridContext = useContext(DataGridContext);
|
||||
@@ -91,7 +90,7 @@ export const TableColumnHeaderNew: React.FC<HeaderRendererProps<any>> = observer
|
||||
|
||||
const model = dataGridContext.model;
|
||||
const columnName = calculatedColumn.name as string;
|
||||
const column = getColumn(Number(calculatedColumn.key), model.getResult(dataGridContext.resultIndex).data);
|
||||
const column = getColumn(Number(calculatedColumn.key), model.getResult(dataGridContext.resultIndex)?.data);
|
||||
|
||||
const loading = model.isLoading();
|
||||
|
||||
@@ -24,4 +24,17 @@
|
||||
.rdg-cell {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.rdg-cell-editing {
|
||||
overflow: visible;
|
||||
height: 24px;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.rdg-editor-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11092,11 +11092,24 @@ react-dom@^16.13.1, react-dom@^16.x.x:
|
||||
prop-types "^15.6.2"
|
||||
scheduler "^0.19.1"
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
||||
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-popper@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.4.tgz#d2ad3d2474ac9f1abf93df3099d408e5aa6a2e22"
|
||||
integrity sha512-NacOu4zWupdQjVXq02XpTD3yFPSfg5a7fex0wa3uGKVkFK7UN6LvVxgcb+xYr56UCuWiNPMH20tntdVdJRwYew==
|
||||
dependencies:
|
||||
react-fast-compare "^3.0.1"
|
||||
warning "^4.0.2"
|
||||
|
||||
react-sanitized-html@^2.x.x:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-sanitized-html/-/react-sanitized-html-2.0.0.tgz#2ac300ae60e8e7cb28378b1233b9630e415d0241"
|
||||
@@ -13350,6 +13363,13 @@ verror@1.10.0:
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
warning@^4.0.2:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||
integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
|
||||
dependencies:
|
||||
loose-envify "^1.0.0"
|
||||
|
||||
watchpack@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.0.tgz#e63194736bf3aa22026f7b191cd57907b0f9f696"
|
||||
|
||||
Reference in New Issue
Block a user