Skip to content

Commit

Permalink
feat(ssh_tunnel): SQLAlchemy Form UI (#22513)
Browse files Browse the repository at this point in the history
Co-authored-by: hughhhh <hughmil3s@gmail.com>
Co-authored-by: Elizabeth Thompson <eschutho@gmail.com>
  • Loading branch information
3 people authored Jan 11, 2023
1 parent 0908fd2 commit 5399365
Show file tree
Hide file tree
Showing 16 changed files with 701 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export enum FeatureFlag {
USE_ANALAGOUS_COLORS = 'USE_ANALAGOUS_COLORS',
UX_BETA = 'UX_BETA',
VERSIONED_EXPORT = 'VERSIONED_EXPORT',
SSH_TUNNELING = 'SSH_TUNNELING',
}
export type ScheduleQueriesProps = {
JSONSCHEMA: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface FieldPropTypes {
onRemoveTableCatalog: (idx: number) => void;
} & {
onExtraInputChange: (value: any) => void;
onSSHTunnelParametersChange: (value: any) => string;
};
validationErrors: JsonObject | null;
getValidation: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
/**
* 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, { 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 { 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 '.';

const StyledDiv = styled.div`
padding-top: ${({ theme }) => theme.gridUnit * 2}px;
label {
color: ${({ theme }) => theme.colors.grayscale.base};
text-transform: uppercase;
margin-bottom: ${({ theme }) => theme.gridUnit * 2}px;
}
`;

const StyledRow = styled(Row)`
padding-bottom: ${({ theme }) => theme.gridUnit * 2}px;
`;

const StyledFormItem = styled(AntdForm.Item)`
margin-bottom: 0 !important;
`;

const StyledInputPassword = styled(AntdInput.Password)`
margin: ${({ theme }) => `${theme.gridUnit}px 0 ${theme.gridUnit * 2}px`};
`;

const SSHTunnelForm = ({
db,
dbFetched,
isEditMode,
sshTunneling,
onSSHTunnelParametersChange,
setSSHTunnelLoginMethod,
removeSSHTunnelConfig,
}: {
db: DatabaseObject | null;
dbFetched: DatabaseObject | null;
isEditMode: boolean;
sshTunneling: 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={
!sshTunneling || (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}>
<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>
);
};
export default SSHTunnelForm;
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { EventHandler, ChangeEvent, MouseEvent } from 'react';
import React, { EventHandler, ChangeEvent, MouseEvent, ReactNode } from 'react';
import { t, SupersetTheme } from '@superset-ui/core';
import SupersetText from 'src/utils/textUtils';
import Button from 'src/components/Button';
Expand All @@ -30,12 +30,14 @@ const SqlAlchemyTab = ({
testConnection,
conf,
testInProgress = false,
children,
}: {
db: DatabaseObject | null;
onInputChange: EventHandler<ChangeEvent<HTMLInputElement>>;
testConnection: EventHandler<MouseEvent<HTMLElement>>;
conf: { SQLALCHEMY_DOCS_URL: string; SQLALCHEMY_DISPLAY_TEXT: string };
testInProgress?: boolean;
children?: ReactNode;
}) => {
let fallbackDocsUrl;
let fallbackDisplayText;
Expand Down Expand Up @@ -99,6 +101,7 @@ const SqlAlchemyTab = ({
{t('for more information on how to structure your URI.')}
</div>
</StyledInputContainer>
{children}
<Button
onClick={testConnection}
loading={testInProgress}
Expand Down
Loading

0 comments on commit 5399365

Please sign in to comment.