fix: SSH Tunnel configuration settings (#27186)

This commit is contained in:
Geido
2024-03-11 16:56:54 +01:00
committed by GitHub
parent fde93dcf08
commit 89e89de341
24 changed files with 871 additions and 271 deletions

View File

@@ -17,12 +17,11 @@
* under the License.
*/
import React from 'react';
import { isEmpty } from 'lodash';
import { SupersetTheme, t } from '@superset-ui/core';
import { AntdSwitch } from 'src/components';
import InfoTooltip from 'src/components/InfoTooltip';
import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput';
import { FieldPropTypes } from '.';
import { FieldPropTypes } from '../../types';
import { toggleStyle, infoTooltip } from '../styles';
export const hostField = ({
@@ -252,35 +251,3 @@ export const forceSSLField = ({
/>
</div>
);
export const SSHTunnelSwitch = ({
isEditMode,
changeMethods,
clearValidationErrors,
db,
}: FieldPropTypes) => (
<div css={(theme: SupersetTheme) => infoTooltip(theme)}>
<AntdSwitch
disabled={isEditMode && !isEmpty(db?.ssh_tunnel)}
checked={db?.parameters?.ssh}
onChange={changed => {
changeMethods.onParametersChange({
target: {
type: 'toggle',
name: 'ssh',
checked: true,
value: changed,
},
});
clearValidationErrors();
}}
data-test="ssh-tunnel-switch"
/>
<span css={toggleStyle}>{t('SSH Tunnel')}</span>
<InfoTooltip
tooltip={t('SSH Tunnel configuration parameters')}
placement="right"
viewBox="0 -5 24 24"
/>
</div>
);

View File

@@ -22,7 +22,7 @@ import { AntdButton, AntdSelect } from 'src/components';
import InfoTooltip from 'src/components/InfoTooltip';
import FormLabel from 'src/components/Form/FormLabel';
import Icons from 'src/components/Icons';
import { FieldPropTypes } from '.';
import { FieldPropTypes } from '../../types';
import { infoTooltip, labelMarginBottom, CredentialInfoForm } from '../styles';
enum CredentialInfoOptions {

View File

@@ -21,9 +21,8 @@ import { css, SupersetTheme, t } from '@superset-ui/core';
import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput';
import FormLabel from 'src/components/Form/FormLabel';
import Icons from 'src/components/Icons';
import { FieldPropTypes } from '.';
import { StyledFooterButton, StyledCatalogTable } from '../styles';
import { CatalogObject } from '../../types';
import { CatalogObject, FieldPropTypes } from '../../types';
export const TableCatalog = ({
required,

View File

@@ -19,7 +19,7 @@
import React from 'react';
import { t } from '@superset-ui/core';
import ValidatedInput from 'src/components/Form/LabeledErrorBoundInput';
import { FieldPropTypes } from '.';
import { FieldPropTypes } from '../../types';
const FIELD_TEXT_MAP = {
account: {

View File

@@ -17,7 +17,11 @@
* under the License.
*/
import React, { FormEvent } from 'react';
import { SupersetTheme, JsonObject } from '@superset-ui/core';
import {
SupersetTheme,
JsonObject,
getExtensionsRegistry,
} from '@superset-ui/core';
import { InputProps } from 'antd/lib/input';
import { Form } from 'src/components/Form';
import {
@@ -31,13 +35,13 @@ import {
portField,
queryField,
usernameField,
SSHTunnelSwitch,
} from './CommonParameters';
import { validatedInputField } from './ValidatedInputField';
import { EncryptedField } from './EncryptedField';
import { TableCatalog } from './TableCatalog';
import { formScrollableStyles, validatedFormStyles } from '../styles';
import { DatabaseForm, DatabaseObject } from '../../types';
import SSHTunnelSwitch from '../SSHTunnelSwitch';
export const FormFieldOrder = [
'host',
@@ -59,34 +63,10 @@ export const FormFieldOrder = [
'ssh',
];
export interface FieldPropTypes {
required: boolean;
hasTooltip?: boolean;
tooltipText?: (value: any) => string;
placeholder?: string;
onParametersChange: (value: any) => string;
onParametersUploadFileChange: (value: any) => string;
changeMethods: { onParametersChange: (value: any) => string } & {
onChange: (value: any) => string;
} & {
onQueryChange: (value: any) => string;
} & { onParametersUploadFileChange: (value: any) => string } & {
onAddTableCatalog: () => void;
onRemoveTableCatalog: (idx: number) => void;
} & {
onExtraInputChange: (value: any) => void;
onSSHTunnelParametersChange: (value: any) => string;
};
validationErrors: JsonObject | null;
getValidation: () => void;
clearValidationErrors: () => void;
db?: DatabaseObject;
field: string;
isEditMode?: boolean;
sslForced?: boolean;
defaultDBName?: string;
editNewDb?: boolean;
}
const extensionsRegistry = getExtensionsRegistry();
const SSHTunnelSwitchComponent =
extensionsRegistry.get('ssh_tunnel.form.switch') ?? SSHTunnelSwitch;
const FORM_FIELD_MAP = {
host: hostField,
@@ -105,7 +85,7 @@ const FORM_FIELD_MAP = {
warehouse: validatedInputField,
role: validatedInputField,
account: validatedInputField,
ssh: SSHTunnelSwitch,
ssh: SSHTunnelSwitchComponent,
};
interface DatabaseConnectionFormProps {
@@ -138,7 +118,7 @@ interface DatabaseConnectionFormProps {
}
const DatabaseConnectionForm = ({
dbModel: { parameters },
dbModel,
db,
editNewDb,
getPlaceholder,
@@ -154,47 +134,51 @@ const DatabaseConnectionForm = ({
sslForced,
validationErrors,
clearValidationErrors,
}: DatabaseConnectionFormProps) => (
<Form>
<div
// @ts-ignore
css={(theme: SupersetTheme) => [
formScrollableStyles,
validatedFormStyles(theme),
]}
>
{parameters &&
FormFieldOrder.filter(
(key: string) =>
Object.keys(parameters.properties).includes(key) ||
key === 'database_name',
).map(field =>
FORM_FIELD_MAP[field]({
required: parameters.required?.includes(field),
changeMethods: {
onParametersChange,
onChange,
onQueryChange,
onParametersUploadFileChange,
onAddTableCatalog,
onRemoveTableCatalog,
onExtraInputChange,
},
validationErrors,
getValidation,
clearValidationErrors,
db,
key: field,
field,
isEditMode,
sslForced,
editNewDb,
placeholder: getPlaceholder ? getPlaceholder(field) : undefined,
}),
)}
</div>
</Form>
);
}: DatabaseConnectionFormProps) => {
const parameters = dbModel?.parameters;
return (
<Form>
<div
// @ts-ignore
css={(theme: SupersetTheme) => [
formScrollableStyles,
validatedFormStyles(theme),
]}
>
{parameters &&
FormFieldOrder.filter(
(key: string) =>
Object.keys(parameters.properties).includes(key) ||
key === 'database_name',
).map(field =>
FORM_FIELD_MAP[field]({
required: parameters.required?.includes(field),
changeMethods: {
onParametersChange,
onChange,
onQueryChange,
onParametersUploadFileChange,
onAddTableCatalog,
onRemoveTableCatalog,
onExtraInputChange,
},
validationErrors,
getValidation,
clearValidationErrors,
db,
key: field,
field,
isEditMode,
sslForced,
editNewDb,
placeholder: getPlaceholder ? getPlaceholder(field) : undefined,
}),
)}
</div>
</Form>
);
};
export const FormFieldMap = FORM_FIELD_MAP;
export default DatabaseConnectionForm;

View File

@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { EventHandler, ChangeEvent, useState } from 'react';
import React, { useState } from 'react';
import { t, styled } from '@superset-ui/core';
import { AntdForm, Col, Row } from 'src/components';
import { Form, FormLabel } from 'src/components/Form';
@@ -24,7 +24,7 @@ import { Radio } from 'src/components/Radio';
import { Input, TextArea } from 'src/components/Input';
import { Input as AntdInput, Tooltip } from 'antd';
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
import { DatabaseObject } from '../types';
import { DatabaseObject, FieldPropTypes } from '../types';
import { AuthType } from '.';
const StyledDiv = styled.div`
@@ -54,9 +54,7 @@ const SSHTunnelForm = ({
setSSHTunnelLoginMethod,
}: {
db: DatabaseObject | null;
onSSHTunnelParametersChange: EventHandler<
ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
>;
onSSHTunnelParametersChange: FieldPropTypes['changeMethods']['onSSHTunnelParametersChange'];
setSSHTunnelLoginMethod: (method: AuthType) => void;
}) => {
const [usePassword, setUsePassword] = useState<AuthType>(AuthType.Password);
@@ -86,9 +84,9 @@ const SSHTunnelForm = ({
</FormLabel>
<Input
name="server_port"
type="text"
placeholder={t('22')}
value={db?.ssh_tunnel?.server_port || ''}
type="number"
value={db?.ssh_tunnel?.server_port}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-server_port-input"
/>

View File

@@ -0,0 +1,162 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import React from 'react';
import { render, screen } from 'spec/helpers/testing-library';
import userEvent from '@testing-library/user-event';
import SSHTunnelSwitch from './SSHTunnelSwitch';
import { DatabaseForm, DatabaseObject } from '../types';
jest.mock('@superset-ui/core', () => ({
...jest.requireActual('@superset-ui/core'),
isFeatureEnabled: jest.fn().mockReturnValue(true),
}));
jest.mock('src/components', () => ({
AntdSwitch: ({
checked,
onChange,
}: {
checked: boolean;
onChange: (checked: boolean) => void;
}) => (
<button
onClick={() => onChange(!checked)}
aria-checked={checked}
role="switch"
type="button"
>
{checked ? 'ON' : 'OFF'}
</button>
),
}));
const mockChangeMethods = {
onParametersChange: jest.fn(),
};
const mockDbModel = {
engine: 'mysql',
engine_information: {
disable_ssh_tunneling: false,
},
} as DatabaseForm;
const defaultDb = {
parameters: { ssh: false },
ssh_tunnel: {},
engine: 'mysql',
} as DatabaseObject;
afterEach(() => {
jest.clearAllMocks();
});
test('Renders SSH Tunnel switch enabled by default and toggles its state', () => {
render(
<SSHTunnelSwitch
changeMethods={mockChangeMethods}
clearValidationErrors={jest.fn}
db={defaultDb}
dbModel={mockDbModel}
/>,
);
const switchButton = screen.getByRole('switch');
expect(switchButton).toHaveTextContent('OFF');
userEvent.click(switchButton);
expect(mockChangeMethods.onParametersChange).toHaveBeenCalledWith({
target: { type: 'toggle', name: 'ssh', checked: true, value: true },
});
expect(switchButton).toHaveTextContent('ON');
});
test('Does not render if SSH Tunnel is disabled', () => {
render(
<SSHTunnelSwitch
changeMethods={mockChangeMethods}
clearValidationErrors={jest.fn}
db={defaultDb}
dbModel={{
...mockDbModel,
engine_information: {
disable_ssh_tunneling: true,
supports_file_upload: false,
},
}}
/>,
);
expect(screen.queryByRole('switch')).not.toBeInTheDocument();
});
test('Checks the switch based on db.parameters.ssh', () => {
const dbWithSSHTunnelEnabled = {
...defaultDb,
parameters: { ssh: true },
} as DatabaseObject;
render(
<SSHTunnelSwitch
changeMethods={mockChangeMethods}
clearValidationErrors={jest.fn}
db={dbWithSSHTunnelEnabled}
dbModel={mockDbModel}
/>,
);
expect(screen.getByRole('switch')).toHaveTextContent('ON');
});
test('Calls onParametersChange with true if SSH Tunnel info exists', () => {
const dbWithSSHTunnelInfo = {
...defaultDb,
parameters: { ssh: undefined },
ssh_tunnel: { host: 'example.com' },
} as DatabaseObject;
render(
<SSHTunnelSwitch
changeMethods={mockChangeMethods}
clearValidationErrors={jest.fn}
db={dbWithSSHTunnelInfo}
dbModel={mockDbModel}
/>,
);
expect(mockChangeMethods.onParametersChange).toHaveBeenCalledWith({
target: { type: 'toggle', name: 'ssh', checked: true, value: true },
});
});
test('Displays tooltip text on hover over the InfoTooltip', async () => {
const tooltipText = 'SSH Tunnel configuration parameters';
render(
<SSHTunnelSwitch
changeMethods={mockChangeMethods}
clearValidationErrors={jest.fn}
db={defaultDb}
dbModel={mockDbModel}
/>,
);
const infoTooltipTrigger = screen.getByRole('img', {
name: 'info-solid_small',
});
expect(infoTooltipTrigger).toBeInTheDocument();
userEvent.hover(infoTooltipTrigger);
const tooltip = await screen.findByText(tooltipText);
expect(tooltip).toBeInTheDocument();
});

View File

@@ -16,35 +16,73 @@
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { t, SupersetTheme, SwitchProps } from '@superset-ui/core';
import React, { useEffect, useState } from 'react';
import {
t,
SupersetTheme,
isFeatureEnabled,
FeatureFlag,
} from '@superset-ui/core';
import { AntdSwitch } from 'src/components';
import InfoTooltip from 'src/components/InfoTooltip';
import { isEmpty } from 'lodash';
import { ActionType } from '.';
import { infoTooltip, toggleStyle } from './styles';
import { SwitchProps } from '../types';
const SSHTunnelSwitch = ({
isEditMode,
dbFetched,
useSSHTunneling,
setUseSSHTunneling,
setDB,
isSSHTunneling,
}: SwitchProps) =>
isSSHTunneling ? (
clearValidationErrors,
changeMethods,
db,
dbModel,
}: SwitchProps) => {
const [isChecked, setChecked] = useState(false);
const sshTunnelEnabled = isFeatureEnabled(FeatureFlag.SshTunneling);
const disableSSHTunnelingForEngine =
dbModel?.engine_information?.disable_ssh_tunneling || false;
const isSSHTunnelEnabled = sshTunnelEnabled && !disableSSHTunnelingForEngine;
const handleOnChange = (changed: boolean) => {
setChecked(changed);
changeMethods.onParametersChange({
target: {
type: 'toggle',
name: 'ssh',
checked: true,
value: changed,
},
});
clearValidationErrors();
};
useEffect(() => {
if (isSSHTunnelEnabled && db?.parameters?.ssh !== undefined) {
setChecked(db.parameters.ssh);
}
}, [db?.parameters?.ssh, isSSHTunnelEnabled]);
useEffect(() => {
if (
isSSHTunnelEnabled &&
db?.parameters?.ssh === undefined &&
!isEmpty(db?.ssh_tunnel)
) {
// reflecting the state of the ssh tunnel on first load
changeMethods.onParametersChange({
target: {
type: 'toggle',
name: 'ssh',
checked: true,
value: true,
},
});
}
}, [changeMethods, db?.parameters?.ssh, db?.ssh_tunnel, isSSHTunnelEnabled]);
return isSSHTunnelEnabled ? (
<div css={(theme: SupersetTheme) => infoTooltip(theme)}>
<AntdSwitch
disabled={isEditMode && !isEmpty(dbFetched?.ssh_tunnel)}
checked={useSSHTunneling}
onChange={changed => {
setUseSSHTunneling(changed);
if (!changed) {
setDB({
type: ActionType.RemoveSSHTunnelConfig,
});
}
}}
checked={isChecked}
onChange={handleOnChange}
data-test="ssh-tunnel-switch"
/>
<span css={toggleStyle}>{t('SSH Tunnel')}</span>
@@ -55,4 +93,6 @@ const SSHTunnelSwitch = ({
/>
</div>
) : null;
};
export default SSHTunnelSwitch;

View File

@@ -16,6 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
// TODO: These tests should be made atomic in separate files
import React from 'react';
import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
@@ -1227,9 +1230,9 @@ describe('DatabaseModal', () => {
const SSHTunnelServerPortInput = screen.getByTestId(
'ssh-tunnel-server_port-input',
);
expect(SSHTunnelServerPortInput).toHaveValue('');
expect(SSHTunnelServerPortInput).toHaveValue(null);
userEvent.type(SSHTunnelServerPortInput, '22');
expect(SSHTunnelServerPortInput).toHaveValue('22');
expect(SSHTunnelServerPortInput).toHaveValue(22);
const SSHTunnelUsernameInput = screen.getByTestId(
'ssh-tunnel-username-input',
);
@@ -1263,9 +1266,9 @@ describe('DatabaseModal', () => {
const SSHTunnelServerPortInput = screen.getByTestId(
'ssh-tunnel-server_port-input',
);
expect(SSHTunnelServerPortInput).toHaveValue('');
expect(SSHTunnelServerPortInput).toHaveValue(null);
userEvent.type(SSHTunnelServerPortInput, '22');
expect(SSHTunnelServerPortInput).toHaveValue('22');
expect(SSHTunnelServerPortInput).toHaveValue(22);
const SSHTunnelUsernameInput = screen.getByTestId(
'ssh-tunnel-username-input',
);

View File

@@ -20,8 +20,6 @@ import {
t,
styled,
SupersetTheme,
FeatureFlag,
isFeatureEnabled,
getExtensionsRegistry,
} from '@superset-ui/core';
import React, {
@@ -31,6 +29,7 @@ import React, {
useState,
useReducer,
Reducer,
useCallback,
} from 'react';
import { useHistory } from 'react-router-dom';
import { setItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
@@ -65,6 +64,7 @@ import {
CatalogObject,
Engines,
ExtraJson,
CustomTextType,
} from '../types';
import ExtraOptions from './ExtraOptions';
import SqlAlchemyForm from './SqlAlchemyForm';
@@ -208,8 +208,8 @@ export type DBReducerActionType =
| {
type:
| ActionType.Reset
| ActionType.AddTableCatalogSheet
| ActionType.RemoveSSHTunnelConfig;
| ActionType.RemoveSSHTunnelConfig
| ActionType.AddTableCatalogSheet;
}
| {
type: ActionType.RemoveTableCatalogSheet;
@@ -595,7 +595,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const SSHTunnelSwitchComponent =
extensionsRegistry.get('ssh_tunnel.form.switch') ?? SSHTunnelSwitch;
const [useSSHTunneling, setUseSSHTunneling] = useState<boolean>(false);
const [useSSHTunneling, setUseSSHTunneling] = useState<boolean | undefined>(
undefined,
);
let dbConfigExtraExtension = extensionsRegistry.get(
'databaseconnection.extraOption',
@@ -618,14 +620,6 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const dbImages = getDatabaseImages();
const connectionAlert = getConnectionAlert();
const isEditMode = !!databaseId;
const disableSSHTunnelingForEngine = (
availableDbs?.databases?.find(
(DB: DatabaseObject) =>
DB.backend === db?.engine || DB.engine === db?.engine,
) as DatabaseObject
)?.engine_information?.disable_ssh_tunneling;
const isSSHTunneling =
isFeatureEnabled(FeatureFlag.SshTunneling) && !disableSSHTunnelingForEngine;
const hasAlert =
connectionAlert || !!(db?.engine && engineSpecificAlertMapping[db.engine]);
const useSqlAlchemyForm =
@@ -659,7 +653,13 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
extra: db?.extra,
masked_encrypted_extra: db?.masked_encrypted_extra || '',
server_cert: db?.server_cert || undefined,
ssh_tunnel: db?.ssh_tunnel || undefined,
ssh_tunnel:
!isEmpty(db?.ssh_tunnel) && useSSHTunneling
? {
...db.ssh_tunnel,
server_port: Number(db.ssh_tunnel!.server_port),
}
: undefined,
};
setTestInProgress(true);
testDatabaseConnection(
@@ -687,10 +687,36 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
return false;
};
const onChange = useCallback(
(
type: DBReducerActionType['type'],
payload: CustomTextType | DBReducerPayloadType,
) => {
setDB({ type, payload } as DBReducerActionType);
},
[],
);
const handleClearValidationErrors = useCallback(() => {
setValidationErrors(null);
}, [setValidationErrors]);
const handleParametersChange = useCallback(
({ target }: { target: HTMLInputElement }) => {
onChange(ActionType.ParametersChange, {
type: target.type,
name: target.name,
checked: target.checked,
value: target.value,
});
},
[onChange],
);
const onClose = () => {
setDB({ type: ActionType.Reset });
setHasConnectedDb(false);
setValidationErrors(null); // reset validation errors on close
handleClearValidationErrors(); // reset validation errors on close
clearError();
setEditNewDb(false);
setFileList([]);
@@ -705,7 +731,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
setSSHTunnelPrivateKeys({});
setSSHTunnelPrivateKeyPasswords({});
setConfirmedOverwrite(false);
setUseSSHTunneling(false);
setUseSSHTunneling(undefined);
onHide();
};
@@ -729,12 +755,11 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
setImportingErrorMessage(msg);
});
const onChange = (type: any, payload: any) => {
setDB({ type, payload } as DBReducerActionType);
};
const onSave = async () => {
let dbConfigExtraExtensionOnSaveError;
setLoading(true);
dbConfigExtraExtension
?.onSave(extraExtensionComponentState, db)
.then(({ error }: { error: any }) => {
@@ -743,6 +768,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
addDangerToast(error);
}
});
if (dbConfigExtraExtensionOnSaveError) {
setLoading(false);
return;
@@ -762,17 +788,13 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
});
}
// only do validation for non ssh tunnel connections
if (!dbToUpdate?.ssh_tunnel) {
// make sure that button spinner animates
setLoading(true);
const errors = await getValidation(dbToUpdate, true);
if ((validationErrors && !isEmpty(validationErrors)) || errors) {
setLoading(false);
return;
}
// end spinner animation
const errors = await getValidation(dbToUpdate, true);
if (!isEmpty(validationErrors) || errors?.length) {
addDangerToast(
t('Connection failed, please check your connection settings.'),
);
setLoading(false);
return;
}
const parameters_schema = isEditMode
@@ -829,7 +851,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
});
}
setLoading(true);
// strictly checking for false as an indication that the toggle got unchecked
if (useSSHTunneling === false) {
// remove ssh tunnel
dbToUpdate.ssh_tunnel = null;
}
if (db?.id) {
const result = await updateResource(
db.id as number,
@@ -1282,10 +1309,10 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
}, [sshPrivateKeyPasswordNeeded]);
useEffect(() => {
if (db && isSSHTunneling) {
setUseSSHTunneling(!isEmpty(db?.ssh_tunnel));
if (db?.parameters?.ssh !== undefined) {
setUseSSHTunneling(db.parameters.ssh);
}
}, [db, isSSHTunneling]);
}, [db?.parameters?.ssh]);
const onDbImport = async (info: UploadChangeParam) => {
setImportingErrorMessage('');
@@ -1550,17 +1577,14 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const renderSSHTunnelForm = () => (
<SSHTunnelForm
db={db as DatabaseObject}
onSSHTunnelParametersChange={({
target,
}: {
target: HTMLInputElement | HTMLTextAreaElement;
}) =>
onSSHTunnelParametersChange={({ target }) => {
onChange(ActionType.ParametersSSHTunnelChange, {
type: target.type,
name: target.name,
value: target.value,
})
}
});
handleClearValidationErrors();
}}
setSSHTunnelLoginMethod={(method: AuthType) =>
setDB({
type: ActionType.SetSSHTunnelLoginMethod,
@@ -1623,14 +1647,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
payload: { indexToDelete: idx },
});
}}
onParametersChange={({ target }: { target: HTMLInputElement }) =>
onChange(ActionType.ParametersChange, {
type: target.type,
name: target.name,
checked: target.checked,
value: target.value,
})
}
onParametersChange={handleParametersChange}
onChange={({ target }: { target: HTMLInputElement }) =>
onChange(ActionType.TextChange, {
name: target.name,
@@ -1640,9 +1657,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
getValidation={() => getValidation(db)}
validationErrors={validationErrors}
getPlaceholder={getPlaceholder}
clearValidationErrors={() => setValidationErrors(null)}
clearValidationErrors={handleClearValidationErrors}
/>
{db?.parameters?.ssh && (
{useSSHTunneling && (
<SSHTunnelContainer>{renderSSHTunnelForm()}</SSHTunnelContainer>
)}
</>
@@ -1792,13 +1809,12 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
testInProgress={testInProgress}
>
<SSHTunnelSwitchComponent
isEditMode={isEditMode}
dbFetched={dbFetched}
disableSSHTunnelingForEngine={disableSSHTunnelingForEngine}
useSSHTunneling={useSSHTunneling}
setUseSSHTunneling={setUseSSHTunneling}
setDB={setDB}
isSSHTunneling={isSSHTunneling}
dbModel={dbModel}
db={db as DatabaseObject}
changeMethods={{
onParametersChange: handleParametersChange,
}}
clearValidationErrors={handleClearValidationErrors}
/>
{useSSHTunneling && renderSSHTunnelForm()}
</SqlAlchemyForm>