Skip to content

Commit

Permalink
wires up sshconfig ui
Browse files Browse the repository at this point in the history
  • Loading branch information
Amogh-Bharadwaj committed Dec 12, 2023
1 parent e420f96 commit 235ee42
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 29 deletions.
1 change: 1 addition & 0 deletions nexus/analyzer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,7 @@ fn parse_db_options(
.to_string(),
metadata_schema: opts.get("metadata_schema").map(|s| s.to_string()),
transaction_snapshot: "".to_string(),
ssh_config: None,
};
let config = Config::PostgresConfig(postgres_config);
Some(config)
Expand Down
3 changes: 2 additions & 1 deletion nexus/catalog/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use postgres_connection::{connect_postgres, get_pg_connection_string};
use prost::Message;
use pt::{
flow_model::{FlowJob, QRepFlowJob},
peerdb_peers::PostgresConfig,
peerdb_peers::{PostgresConfig, SshConfig},
peerdb_peers::{peer::Config, DbType, Peer},
};
use serde_json::Value;
Expand Down Expand Up @@ -75,6 +75,7 @@ impl CatalogConfig {
database: self.database.clone(),
transaction_snapshot: "".to_string(),
metadata_schema: Some("".to_string()),
ssh_config: None
}
}

Expand Down
1 change: 1 addition & 0 deletions ui/app/dto/PeersDTO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ export type CatalogPeer = {
type: number;
options: Buffer;
};
export type PeerSetter = React.Dispatch<React.SetStateAction<PeerConfig>>;
3 changes: 1 addition & 2 deletions ui/app/peers/create/[peerType]/helpers/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PeerConfig } from '@/app/dto/PeersDTO';
import { PeerSetter } from '@/components/ConfigForm';
import { PeerConfig, PeerSetter } from '@/app/dto/PeersDTO';
import { blankBigquerySetting } from './bq';
import { blankPostgresSetting } from './pg';
import { blankS3Setting } from './s3';
Expand Down
48 changes: 47 additions & 1 deletion ui/app/peers/create/[peerType]/helpers/pg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PostgresConfig } from '@/grpc_generated/peers';
import { PostgresConfig, SSHConfig } from '@/grpc_generated/peers';
import { Dispatch, SetStateAction } from 'react';
import { PeerSetting } from './common';

export const postgresSetting: PeerSetting[] = [
Expand Down Expand Up @@ -48,6 +49,51 @@ export const postgresSetting: PeerSetting[] = [
},
];

type sshSetter = Dispatch<SetStateAction<SSHConfig>>;
export const sshSetting = [
{
label: 'Host',
stateHandler: (value: string, setter: sshSetter) =>
setter((curr: SSHConfig) => ({ ...curr, host: value })),
tips: 'Specifies the IP host name or address of your instance.',
},
{
label: 'Port',
stateHandler: (value: string, setter: sshSetter) =>
setter((curr) => ({ ...curr, port: parseInt(value, 10) })),
type: 'number',
default: 5432,
tips: 'Specifies the TCP/IP port or local Unix domain socket file extension on which clients can connect.',
},
{
label: 'User',
stateHandler: (value: string, setter: sshSetter) =>
setter((curr) => ({ ...curr, user: value })),
tips: 'Specify the user that we should use to connect to this host.',
},
{
label: 'Password',
stateHandler: (value: string, setter: sshSetter) =>
setter((curr) => ({ ...curr, password: value })),
type: 'password',
tips: 'Password associated with the user you provided.',
},
{
label: 'BASE64 Private Key',
stateHandler: (value: string, setter: sshSetter) =>
setter((curr) => ({ ...curr, database: value })),
tips: 'Private key as a BASE64 string for authentication in order to SSH into your machine.',
},
];

export const blankSSHConfig: SSHConfig = {
host: '',
port: 22,
user: '',
password: '',
privateKey: '',
};

export const blankPostgresSetting: PostgresConfig = {
host: '',
port: 5432,
Expand Down
22 changes: 10 additions & 12 deletions ui/app/peers/create/[peerType]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use client';
import { PeerConfig } from '@/app/dto/PeersDTO';
import BQConfig from '@/components/BigqueryConfig';
import S3ConfigForm from '@/components/S3Form';
import BigqueryForm from '@/components/PeerForms/BigqueryConfig';
import PostgresForm from '@/components/PeerForms/PostgresForm';
import S3Form from '@/components/PeerForms/S3Form';
import SnowflakeForm from '@/components/PeerForms/SnowflakeForm';
import { Button } from '@/lib/Button';
import { ButtonGroup } from '@/lib/ButtonGroup';
import { Label } from '@/lib/Label';
Expand All @@ -12,9 +14,8 @@ import { Tooltip } from '@/lib/Tooltip';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import ConfigForm from '../../../../components/ConfigForm';
import { handleCreate, handleValidate } from './handlers';
import { PeerSetting, getBlankSetting } from './helpers/common';
import { getBlankSetting } from './helpers/common';
import { postgresSetting } from './helpers/pg';
import { snowflakeSetting } from './helpers/sf';

Expand All @@ -36,18 +37,15 @@ export default function CreateConfig({
});
const [loading, setLoading] = useState<boolean>(false);
const configComponentMap = (dbType: string) => {
const configForm = (settingList: PeerSetting[]) => (
<ConfigForm settings={settingList} setter={setConfig} />
);
switch (dbType) {
case 'POSTGRES':
return configForm(postgresSetting);
return <PostgresForm settings={postgresSetting} setter={setConfig} />;
case 'SNOWFLAKE':
return configForm(snowflakeSetting);
return <SnowflakeForm settings={snowflakeSetting} setter={setConfig} />;
case 'BIGQUERY':
return <BQConfig setter={setConfig} />;
return <BigqueryForm setter={setConfig} />;
case 'S3':
return <S3ConfigForm setter={setConfig} />;
return <S3Form setter={setConfig} />;
default:
return <></>;
}
Expand Down Expand Up @@ -103,7 +101,7 @@ export default function CreateConfig({
<Label colorName='lowContrast' variant='subheadline'>
Configuration
</Label>
{dbType && configComponentMap(dbType)}
{configComponentMap(dbType)}
</Panel>
<Panel>
<ButtonGroup>
Expand Down
39 changes: 39 additions & 0 deletions ui/app/peers/create/[peerType]/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,45 @@ export const pgSchema = z.object({
.string()
.max(100, 'Transaction snapshot too long (100 char limit)')
.optional(),
sshConfig: z
.object({
host: z
.string({
required_error: 'SSH Host is required',
invalid_type_error: 'SSH Host must be a string',
})
.min(1, { message: 'SSH Host cannot be empty' })
.max(255, 'SSH Host must be less than 255 characters'),
port: z
.number({
required_error: 'SSH Port is required',
invalid_type_error: 'SSH Port must be a number',
})
.int()
.min(1, 'SSH Port must be a positive integer')
.max(65535, 'SSH Port must be below 65535'),
user: z
.string({
required_error: 'SSH User is required',
invalid_type_error: 'SSH User must be a string',
})
.min(1, 'SSH User must be non-empty')
.max(64, 'SSH User must be less than 64 characters'),
password: z
.string({
required_error: 'SSH Password is required',
invalid_type_error: 'SSH Password must be a string',
})
.min(1, 'SSH Password must be non-empty')
.max(100, 'SSH Password must be less than 100 characters'),
privateKey: z
.string({
required_error: 'SSH Private Key is required',
invalid_type_error: 'SSH Private Key must be a string',
})
.min(1, { message: 'SSH Private Key must be non-empty' }),
})
.optional(),
});

export const sfSchema = z.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';
import { PeerSetter } from '@/app/dto/PeersDTO';
import { blankBigquerySetting } from '@/app/peers/create/[peerType]/helpers/bq';
import { BigqueryConfig } from '@/grpc_generated/peers';
import { Label } from '@/lib/Label';
Expand All @@ -7,13 +8,12 @@ import { TextField } from '@/lib/TextField';
import { Tooltip } from '@/lib/Tooltip';
import Link from 'next/link';
import { useState } from 'react';
import { PeerSetter } from './ConfigForm';
import { InfoPopover } from './InfoPopover';
import { InfoPopover } from '../InfoPopover';

interface BQProps {
setter: PeerSetter;
}
export default function BQConfig(props: BQProps) {
export default function BigqueryForm(props: BQProps) {
const [datasetID, setDatasetID] = useState<string>('');
const handleJSONFile = (file: File) => {
if (file) {
Expand Down
153 changes: 153 additions & 0 deletions ui/components/PeerForms/PostgresForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
'use client';
import { PeerSetter } from '@/app/dto/PeersDTO';
import { PeerSetting } from '@/app/peers/create/[peerType]/helpers/common';
import {
blankSSHConfig,
sshSetting,
} from '@/app/peers/create/[peerType]/helpers/pg';
import { SSHConfig } from '@/grpc_generated/peers';
import { Label } from '@/lib/Label';
import { RowWithTextField } from '@/lib/Layout';
import { Switch } from '@/lib/Switch';
import { TextField } from '@/lib/TextField';
import { Tooltip } from '@/lib/Tooltip';
import { useEffect, useState } from 'react';
import { InfoPopover } from '../InfoPopover';
interface ConfigProps {
settings: PeerSetting[];
setter: PeerSetter;
}

export default function PostgresForm({settings, setter}: ConfigProps) {
const [showSSH, setShowSSH] = useState<boolean>(false);
const [sshConfig, setSSHConfig] = useState<SSHConfig>(blankSSHConfig);

const handleChange = (
e: React.ChangeEvent<HTMLInputElement>,
setting: PeerSetting
) => {
setting.stateHandler(e.target.value, setter);
};

useEffect(() => {
setter((prev) => {
return {
...prev,
sshConfig: showSSH ? sshConfig : undefined,
};
});
}, [sshConfig, setter, showSSH]);

return (
<>
{settings.map((setting, id) => {
return (
<RowWithTextField
key={id}
label={
<Label>
{setting.label}{' '}
{!setting.optional && (
<Tooltip
style={{ width: '100%' }}
content={'This is a required field.'}
>
<Label colorName='lowContrast' colorSet='destructive'>
*
</Label>
</Tooltip>
)}
</Label>
}
action={
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}}
>
<TextField
variant='simple'
type={setting.type}
defaultValue={setting.default}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleChange(e, setting)
}
/>
{setting.tips && (
<InfoPopover tips={setting.tips} link={setting.helpfulLink} />
)}
</div>
}
/>
);
})}

<Label
as='label'
style={{ marginTop: '1rem' }}
variant='subheadline'
colorName='lowContrast'
>
SSH Configuration
</Label>
<Label>
You may provide SSH configuration to connect to your PostgreSQL database
through SSH tunnel.
</Label>
<div style={{ width: '50%', display: 'flex', alignItems: 'center' }}>
<Label variant='subheadline'>Configure SSH Tunnel</Label>
<Switch onCheckedChange={(state) => setShowSSH(state)} />
</div>
{showSSH &&
sshSetting.map((sshParam, index) => (
<RowWithTextField
key={index}
label={
<Label>
{sshParam.label}{' '}
<Tooltip
style={{ width: '100%' }}
content={'This is a required field.'}
>
<Label colorName='lowContrast' colorSet='destructive'>
*
</Label>
</Tooltip>
</Label>
}
action={
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}}
>
<TextField
variant='simple'
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
sshParam.stateHandler(e.target.value, setSSHConfig)
}
defaultValue={
(sshConfig as SSHConfig)[
sshParam.label === 'BASE64 Private Key'
? 'privateKey'
: (sshParam.label.toLowerCase() as
| 'host'
| 'port'
| 'user'
| 'password'
| 'privateKey')
] || ''
}
/>
{sshParam.tips && <InfoPopover tips={sshParam.tips} />}
</div>
}
/>
))}
</>
);
}
Loading

0 comments on commit 235ee42

Please sign in to comment.