Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssh_tunnel): SSH Tunnel Switch extension #22967

Merged
merged 2 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ interface MenuObjectChildProps {
disable?: boolean;
}

export interface SwitchProps {
isEditMode: boolean;
dbFetched: any;
disableSSHTunnelingForEngine?: boolean;
useSSHTunneling: boolean;
setUseSSHTunneling: React.Dispatch<React.SetStateAction<boolean>>;
setDB: React.Dispatch<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOTE for other reviewers: @Antonio-RiveroMartnez and I discussed this and this any is a current necessity due to ExtensionsRegistry being in ui-core, and the DatabaseObject Type being in Superset-frontend/src and we do not want to duplicate Types, or create a circular dependency. Moving the DatabaseObject Type or resolving this by moving ExtensionRegistry to superset seemed out of scope for this PR but is being looked at in effort to improve the extensions capability.

isSSHTunneling: boolean;
}

type ConfigDetailsProps = {
embeddedId: string;
};
Expand All @@ -69,6 +79,7 @@ export type Extensions = Partial<{
'welcome.message': React.ComponentType;
'welcome.banner': React.ComponentType;
'welcome.main.replacement': React.ComponentType;
'ssh_tunnel.form.switch': React.ComponentType<SwitchProps>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Antonio-RiveroMartnez this is a great improvement to add props typing for extension points to create a protected contract, well done! I would like to make this a standard pattern to cover all the extension points in the future.

}>;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,13 @@
* under the License.
*/
import React, { EventHandler, ChangeEvent, useState } from 'react';
import { t, SupersetTheme, styled } from '@superset-ui/core';
import { AntdForm, AntdSwitch, Col, Row } from 'src/components';
import InfoTooltip from 'src/components/InfoTooltip';
import { t, styled } from '@superset-ui/core';
import { AntdForm, Col, Row } from 'src/components';
import { Form, FormLabel } from 'src/components/Form';
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 { isEmpty } from 'lodash';
import { infoTooltip, toggleStyle } from './styles';

import { DatabaseObject } from '../types';
import { AuthType } from '.';
Expand Down Expand Up @@ -54,209 +51,175 @@ const StyledInputPassword = styled(AntdInput.Password)`

const SSHTunnelForm = ({
db,
dbFetched,
isEditMode,
isSSHTunneling,
onSSHTunnelParametersChange,
setSSHTunnelLoginMethod,
removeSSHTunnelConfig,
}: {
db: DatabaseObject | null;
dbFetched: DatabaseObject | null;
isEditMode: boolean;
isSSHTunneling: boolean;
onSSHTunnelParametersChange: EventHandler<
ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
>;
setSSHTunnelLoginMethod: (method: AuthType) => void;
removeSSHTunnelConfig: () => void;
}) => {
const [useSSHTunneling, setUseSSHTunneling] = useState<boolean>(
!isEmpty(db?.ssh_tunnel),
);
const [usePassword, setUsePassword] = useState<AuthType>(AuthType.password);

return (
<Form>
<div css={(theme: SupersetTheme) => infoTooltip(theme)}>
<AntdSwitch
disabled={
!isSSHTunneling || (isEditMode && !isEmpty(dbFetched?.ssh_tunnel))
}
checked={useSSHTunneling}
onChange={changed => {
setUseSSHTunneling(changed);
if (!changed) removeSSHTunnelConfig();
}}
data-test="ssh-tunnel-switch"
/>
<span css={toggleStyle}>SSH Tunnel</span>
<InfoTooltip
tooltip={t('SSH Tunnel configuration parameters')}
placement="right"
viewBox="0 -5 24 24"
/>
</div>
{useSSHTunneling && (
<StyledRow gutter={16}>
<Col xs={24} md={12}>
<StyledDiv>
<FormLabel htmlFor="server_address" required>
{t('SSH Host')}
</FormLabel>
<Input
name="server_address"
type="text"
placeholder={t('e.g. 127.0.0.1')}
value={db?.ssh_tunnel?.server_address || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-server_address-input"
/>
</StyledDiv>
</Col>
<Col xs={24} md={12}>
<StyledDiv>
<FormLabel htmlFor="server_port" required>
{t('SSH Port')}
</FormLabel>
<Input
name="server_port"
type="text"
placeholder={t('22')}
value={db?.ssh_tunnel?.server_port || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-server_port-input"
/>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="username" required>
{t('Username')}
</FormLabel>
<Input
name="username"
type="text"
placeholder={t('e.g. Analytics')}
value={db?.ssh_tunnel?.username || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-username-input"
/>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="use_password" required>
{t('Login with')}
</FormLabel>
<StyledFormItem name="use_password" initialValue={usePassword}>
<Radio.Group
onChange={({ target: { value } }) => {
setUsePassword(value);
setSSHTunnelLoginMethod(value);
}}
>
<Radio
value={AuthType.password}
data-test="ssh-tunnel-use_password-radio"
>
{t('Password')}
</Radio>
<Radio
value={AuthType.privateKey}
data-test="ssh-tunnel-use_private_key-radio"
>
{t('Private Key & Password')}
</Radio>
</Radio.Group>
</StyledFormItem>
</StyledDiv>
</Col>
</StyledRow>
{usePassword === AuthType.password && (
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="password" required>
{t('SSH Password')}
</FormLabel>
<StyledInputPassword
name="password"
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.password || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-password-input"
iconRender={visible =>
visible ? (
<Tooltip title="Hide password.">
<EyeInvisibleOutlined />
</Tooltip>
) : (
<Tooltip title="Show password.">
<EyeOutlined />
</Tooltip>
)
}
role="textbox"
/>
</StyledDiv>
</Col>
</StyledRow>
)}
{usePassword === AuthType.privateKey && (
<>
<StyledRow gutter={16}>
<Col xs={24} md={12}>
<StyledDiv>
<FormLabel htmlFor="server_address" required>
{t('SSH Host')}
</FormLabel>
<Input
name="server_address"
type="text"
placeholder={t('e.g. 127.0.0.1')}
value={db?.ssh_tunnel?.server_address || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-server_address-input"
/>
</StyledDiv>
</Col>
<Col xs={24} md={12}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="server_port" required>
{t('SSH Port')}
<FormLabel htmlFor="private_key" required>
{t('Private Key')}
</FormLabel>
<Input
name="server_port"
type="text"
placeholder={t('22')}
value={db?.ssh_tunnel?.server_port || ''}
<TextArea
name="private_key"
placeholder={t('Paste Private Key here')}
value={db?.ssh_tunnel?.private_key || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-server_port-input"
data-test="ssh-tunnel-private_key-input"
rows={4}
/>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="username" required>
{t('Username')}
<FormLabel htmlFor="private_key_password" required>
{t('Private Key Password')}
</FormLabel>
<Input
name="username"
type="text"
placeholder={t('e.g. Analytics')}
value={db?.ssh_tunnel?.username || ''}
<StyledInputPassword
name="private_key_password"
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.private_key_password || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-username-input"
data-test="ssh-tunnel-private_key_password-input"
iconRender={visible =>
visible ? (
<Tooltip title="Hide password.">
<EyeInvisibleOutlined />
</Tooltip>
) : (
<Tooltip title="Show password.">
<EyeOutlined />
</Tooltip>
)
}
role="textbox"
/>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="use_password" required>
{t('Login with')}
</FormLabel>
<StyledFormItem name="use_password" initialValue={usePassword}>
<Radio.Group
onChange={({ target: { value } }) => {
setUsePassword(value);
setSSHTunnelLoginMethod(value);
}}
>
<Radio
value={AuthType.password}
data-test="ssh-tunnel-use_password-radio"
>
{t('Password')}
</Radio>
<Radio
value={AuthType.privateKey}
data-test="ssh-tunnel-use_private_key-radio"
>
{t('Private Key & Password')}
</Radio>
</Radio.Group>
</StyledFormItem>
</StyledDiv>
</Col>
</StyledRow>
{usePassword === AuthType.password && (
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="password" required>
{t('SSH Password')}
</FormLabel>
<StyledInputPassword
name="password"
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.password || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-password-input"
iconRender={visible =>
visible ? (
<Tooltip title={t('Hide password.')}>
<EyeInvisibleOutlined />
</Tooltip>
) : (
<Tooltip title={t('Show password.')}>
<EyeOutlined />
</Tooltip>
)
}
role="textbox"
/>
</StyledDiv>
</Col>
</StyledRow>
)}
{usePassword === AuthType.privateKey && (
<>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="private_key" required>
{t('Private Key')}
</FormLabel>
<TextArea
name="private_key"
placeholder={t('Paste Private Key here')}
value={db?.ssh_tunnel?.private_key || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-private_key-input"
rows={4}
/>
</StyledDiv>
</Col>
</StyledRow>
<StyledRow gutter={16}>
<Col xs={24}>
<StyledDiv>
<FormLabel htmlFor="private_key_password" required>
{t('Private Key Password')}
</FormLabel>
<StyledInputPassword
name="private_key_password"
placeholder={t('e.g. ********')}
value={db?.ssh_tunnel?.private_key_password || ''}
onChange={onSSHTunnelParametersChange}
data-test="ssh-tunnel-private_key_password-input"
iconRender={visible =>
visible ? (
<Tooltip title="Hide password.">
<EyeInvisibleOutlined />
</Tooltip>
) : (
<Tooltip title="Show password.">
<EyeOutlined />
</Tooltip>
)
}
role="textbox"
/>
</StyledDiv>
</Col>
</StyledRow>
</>
)}
</>
)}
</Form>
Expand Down
Loading