diff --git a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts index 012bb4ebaf9ec..524d5a6d1c14f 100644 --- a/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts +++ b/superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts @@ -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: { diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/index.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/index.tsx index af4923e7b9823..f6284ae67d9a7 100644 --- a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/index.tsx +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/DatabaseConnectionForm/index.tsx @@ -73,6 +73,7 @@ export interface FieldPropTypes { onRemoveTableCatalog: (idx: number) => void; } & { onExtraInputChange: (value: any) => void; + onSSHTunnelParametersChange: (value: any) => string; }; validationErrors: JsonObject | null; getValidation: () => void; diff --git a/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx new file mode 100644 index 0000000000000..d73c00265d0a4 --- /dev/null +++ b/superset-frontend/src/views/CRUD/data/database/DatabaseModal/SSHTunnelForm.tsx @@ -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 + >; + setSSHTunnelLoginMethod: (method: AuthType) => void; + removeSSHTunnelConfig: () => void; +}) => { + const [useSSHTunneling, setUseSSHTunneling] = useState( + !isEmpty(db?.ssh_tunnel), + ); + const [usePassword, setUsePassword] = useState(AuthType.password); + + return ( +
+
infoTooltip(theme)}> + { + setUseSSHTunneling(changed); + if (!changed) removeSSHTunnelConfig(); + }} + data-test="ssh-tunnel-switch" + /> + SSH Tunnel + +
+ {useSSHTunneling && ( + <> + + + + + {t('SSH Host')} + + + + + + + + {t('SSH Port')} + + + + + + + + + + {t('Username')} + + + + + + + + + + {t('Login with')} + + + { + setUsePassword(value); + setSSHTunnelLoginMethod(value); + }} + > + + {t('Password')} + + + {t('Private Key & Password')} + + + + + + + {usePassword === AuthType.password && ( + + + + + {t('SSH Password')} + + + visible ? ( + + + + ) : ( + + + + ) + } + role="textbox" + /> + + + + )} + {usePassword === AuthType.privateKey && ( + <> + + + + + {t('Private Key')} + +