From 5ba704721b19177df8c2d733c35c56a952439e02 Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Mon, 25 Sep 2023 21:40:49 +0530 Subject: [PATCH 01/12] Boilerplate for create peer UI --- ui/app/connectors/create/api.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ui/app/connectors/create/api.ts diff --git a/ui/app/connectors/create/api.ts b/ui/app/connectors/create/api.ts new file mode 100644 index 0000000000..01bf545015 --- /dev/null +++ b/ui/app/connectors/create/api.ts @@ -0,0 +1,12 @@ +import { CreatePeerRequest, ValidatePeerRequest } from '@/grpc_generated/route'; +import { GetFlowServiceClient } from '@/rpc/rpc'; + +export async function validatePeer(req: ValidatePeerRequest) { + let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; + let flowServiceClient = GetFlowServiceClient(flowServiceAddress); +} + +export async function createPeer(req: CreatePeerRequest) { + let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; + let flowServiceClient = GetFlowServiceClient(flowServiceAddress); +} \ No newline at end of file From 834f9b7fd7a0cbb456f76cde48cdebfb1975d74c Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Thu, 28 Sep 2023 16:15:09 +0530 Subject: [PATCH 02/12] feat: apis for validate and create peer --- flow/connectors/core.go | 1 + 1 file changed, 1 insertion(+) diff --git a/flow/connectors/core.go b/flow/connectors/core.go index dbc040a5b2..7b1e69fa36 100644 --- a/flow/connectors/core.go +++ b/flow/connectors/core.go @@ -16,6 +16,7 @@ import ( ) var ErrUnsupportedFunctionality = errors.New("requested connector does not support functionality") +var ErrWrongConfig = errors.New("wrong config for connector") type Connector interface { Close() error From 87162c50fa614ef81e30b0ff400d53b171444178 Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Thu, 28 Sep 2023 16:16:39 +0530 Subject: [PATCH 03/12] ui for create pg peer --- ui/app/{connectors => peers}/create/api.ts | 0 ui/app/peers/create/details/configForm.tsx | 44 ++++++++++ ui/app/peers/create/details/handlers.ts | 46 +++++++++++ ui/app/peers/create/details/helpers/pg.ts | 44 ++++++++++ ui/app/peers/create/details/page.tsx | 82 +++++++++++++++++++ ui/app/peers/create/details/schema.ts | 9 ++ ui/app/peers/create/details/types.ts | 5 ++ ui/app/{connectors => peers}/create/page.tsx | 18 ++-- .../edit/[connectorId]/page.tsx | 12 +-- ui/app/{connectors => peers}/layout.tsx | 0 ui/app/{connectors => peers}/page.tsx | 13 ++- ui/components/SelectSource.tsx | 29 +++++++ ui/components/SidebarComponent.tsx | 4 +- ui/package.json | 1 + ui/yarn.lock | 40 +++++++++ 15 files changed, 327 insertions(+), 20 deletions(-) rename ui/app/{connectors => peers}/create/api.ts (100%) create mode 100644 ui/app/peers/create/details/configForm.tsx create mode 100644 ui/app/peers/create/details/handlers.ts create mode 100644 ui/app/peers/create/details/helpers/pg.ts create mode 100644 ui/app/peers/create/details/page.tsx create mode 100644 ui/app/peers/create/details/schema.ts create mode 100644 ui/app/peers/create/details/types.ts rename ui/app/{connectors => peers}/create/page.tsx (58%) rename ui/app/{connectors => peers}/edit/[connectorId]/page.tsx (97%) rename ui/app/{connectors => peers}/layout.tsx (100%) rename ui/app/{connectors => peers}/page.tsx (91%) create mode 100644 ui/components/SelectSource.tsx diff --git a/ui/app/connectors/create/api.ts b/ui/app/peers/create/api.ts similarity index 100% rename from ui/app/connectors/create/api.ts rename to ui/app/peers/create/api.ts diff --git a/ui/app/peers/create/details/configForm.tsx b/ui/app/peers/create/details/configForm.tsx new file mode 100644 index 0000000000..992f91ccde --- /dev/null +++ b/ui/app/peers/create/details/configForm.tsx @@ -0,0 +1,44 @@ +"use client" +import { Label } from '@/lib/Label'; +import { RowWithTextField } from '@/lib/Layout'; +import { TextField } from '@/lib/TextField'; +import { Dispatch, SetStateAction } from 'react'; +import { PeerConfig, PeerSetter } from './types'; + +interface Field { + label: string, + type?: string +} + +interface ConfigData{ + fields: Field[] + handler: (fieldType: string, value:string, + setter:PeerSetter) => void +} + +interface ConfigProps{ + configData: ConfigData + setter: PeerSetter +} + +export default function PgConfig(props: ConfigProps) { + return( + <> + {props.configData.fields.map((field) => { + return( + + {field.label} + + } + action={ props.configData.handler(field.label, e.target.value, props.setter) + }/>} + /> + ) + }) + } + + ) +} \ No newline at end of file diff --git a/ui/app/peers/create/details/handlers.ts b/ui/app/peers/create/details/handlers.ts new file mode 100644 index 0000000000..2286655961 --- /dev/null +++ b/ui/app/peers/create/details/handlers.ts @@ -0,0 +1,46 @@ +import { DBType } from "@/grpc_generated/peers"; +import { ValidatePeerRequest } from "@/grpc_generated/route"; +import { GetFlowServiceClient } from "@/rpc/rpc"; +import { PeerConfig } from "./types"; +import { pgSchema } from "./schema"; +import { Dispatch, SetStateAction } from "react"; + +const typeMap = (type:string)=> { + switch(type){ + case "POSTGRES": return DBType.POSTGRES + case "SNOWFLAKE": return DBType.SNOWFLAKE + default: return DBType.UNRECOGNIZED + } +} +export const handleValidate = async ( + type:string, + config:PeerConfig, + setErr:Dispatch>, + name?:string + ) => { + if (!name){ + setErr("Peer name is required"); + return; + } + const validity = pgSchema.validate(config); + if(validity.error){ + setErr(validity.error.message); + return; + } + let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; + let flowServiceClient = GetFlowServiceClient(flowServiceAddress); + let req: ValidatePeerRequest = { + peer:{ + "name": name, + "type": typeMap(type), + "postgresConfig": config + } + }; + let peers = await flowServiceClient.listPeers(req); + return peers.peers; +} + + +const handleCreate = () => { + +} \ No newline at end of file diff --git a/ui/app/peers/create/details/helpers/pg.ts b/ui/app/peers/create/details/helpers/pg.ts new file mode 100644 index 0000000000..d7a8007cb8 --- /dev/null +++ b/ui/app/peers/create/details/helpers/pg.ts @@ -0,0 +1,44 @@ +import { Dispatch, SetStateAction } from 'react'; +import { PeerConfig, PeerSetter } from '../types'; + +const pgFields = [{ + label: "Host" +},{ + label: "Port", + type: "number" // type for textfield +},{ + label: "User" +},{ + label: "Password", + type: "password" +},{ + label: "Database", +}] + +const pgHandler = (fieldType: string, value:string, + setter: PeerSetter) => { + switch(fieldType){ + case "Host": + setter(curr => ({...curr, host:value})) + break; + case "Port": + setter(curr => ({...curr, port:parseInt(value, 10)})) + break; + case "User": + setter(curr => ({...curr, user:value})) + break; + case "Password": + setter(curr => ({...curr, password:value})) + break; + case "Database": + setter(curr => ({...curr, database:value})) + break; + default: + break; + } +} + +export const pgConfigData = { + fields: pgFields, + handler: pgHandler +} diff --git a/ui/app/peers/create/details/page.tsx b/ui/app/peers/create/details/page.tsx new file mode 100644 index 0000000000..458ad8c6cd --- /dev/null +++ b/ui/app/peers/create/details/page.tsx @@ -0,0 +1,82 @@ +"use client" +import { Label } from '@/lib/Label'; +import { LayoutMain } from '@/lib/Layout'; +import { Button } from '@/lib/Button'; +import { ButtonGroup } from '@/lib/ButtonGroup'; +import { Panel } from '@/lib/Panel'; +import { RowWithTextField } from '@/lib/Layout'; +import { TextField } from '@/lib/TextField'; +import { useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import PgConfig from './configForm'; +import { useState } from 'react'; +import { pgConfigData } from './helpers/pg'; +import { PeerConfig } from './types'; +import { handleValidate } from './handlers'; + +export default function CreateConfig() { + const searchParams = useSearchParams(); + const dbType = searchParams.get('dbtype')||""; + const [name,setName] = useState(""); + const [config, setConfig] = useState({ + host:"", + port:5432, + user:"", + password:"", + database:"", + transactionSnapshot:"" + }) + const [formErr, setFormErr] = useState("") + const configComponentMap = (dbType:string) => { + switch(dbType){ + case "POSTGRES": + return + default: + return <> + } + } + + return( + + + + + + + + + Name + + } + action={setName(e.currentTarget.textContent||"")}/>} + /> + { + dbType && configComponentMap(dbType) + } + + + + + + + + + + + + + ) +} diff --git a/ui/app/peers/create/details/schema.ts b/ui/app/peers/create/details/schema.ts new file mode 100644 index 0000000000..afa478ca6a --- /dev/null +++ b/ui/app/peers/create/details/schema.ts @@ -0,0 +1,9 @@ +import Joi from 'joi'; + +export const pgSchema = Joi.object({ + host:Joi.string().required(), + port:Joi.number().integer().min(1).required(), + database:Joi.string().min(1).max(100).required(), + user:Joi.string().alphanum().min(1).max(64).required(), + password:Joi.string().min(1).max(100).required(), +}); \ No newline at end of file diff --git a/ui/app/peers/create/details/types.ts b/ui/app/peers/create/details/types.ts new file mode 100644 index 0000000000..3b9f6dc7b0 --- /dev/null +++ b/ui/app/peers/create/details/types.ts @@ -0,0 +1,5 @@ +import { PostgresConfig } from "@/grpc_generated/peers"; +import { Dispatch, SetStateAction } from 'react'; + +export type PeerConfig = PostgresConfig +export type PeerSetter = Dispatch> \ No newline at end of file diff --git a/ui/app/connectors/create/page.tsx b/ui/app/peers/create/page.tsx similarity index 58% rename from ui/app/connectors/create/page.tsx rename to ui/app/peers/create/page.tsx index 30c458489e..156078a64c 100644 --- a/ui/app/connectors/create/page.tsx +++ b/ui/app/peers/create/page.tsx @@ -1,3 +1,4 @@ +"use client" import { Action } from '@/lib/Action'; import { Button } from '@/lib/Button'; import { ButtonGroup } from '@/lib/ButtonGroup'; @@ -5,9 +6,14 @@ import { Icon } from '@/lib/Icon'; import { Label } from '@/lib/Label'; import { LayoutMain, RowWithSelect } from '@/lib/Layout'; import { Panel } from '@/lib/Panel'; -import { Select } from '@/lib/Select'; +import SelectSource from '../../../components/SelectSource'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation' +import Link from 'next/link'; export default function CreatePeer() { + const [peerType, setPeerType] = useState("") + const router = useRouter(); return ( @@ -15,7 +21,7 @@ export default function CreatePeer() { - }>Learn about peers + } href='https://docs.peerdb.io/sql/commands/create-peer' target='_blank'>Learn about peers } - action={ setPeerType(val)} children={ + dbTypes.map((dbType, id) => { + return( + + {dbType} + + ) + }) + }/> + ) +} \ No newline at end of file diff --git a/ui/components/SidebarComponent.tsx b/ui/components/SidebarComponent.tsx index 074b6e3d88..7ae16913be 100644 --- a/ui/components/SidebarComponent.tsx +++ b/ui/components/SidebarComponent.tsx @@ -31,10 +31,10 @@ export default function SidebarComponent() { } > - Connectors + Peers Date: Fri, 29 Sep 2023 20:17:34 +0530 Subject: [PATCH 04/12] create peer for postgres --- ui/app/api/peers/create/route.ts | 29 +++++ ui/app/api/peers/peerTypeMap.ts | 12 ++ ui/app/api/peers/validate/route.ts | 29 +++++ ui/app/peers/create/api.ts | 12 -- .../peers/create/configuration/configForm.tsx | 40 +++++++ ui/app/peers/create/configuration/handlers.ts | 83 ++++++++++++++ .../peers/create/configuration/helpers/pg.ts | 36 ++++++ ui/app/peers/create/configuration/page.tsx | 108 ++++++++++++++++++ ui/app/peers/create/configuration/schema.ts | 20 ++++ ui/app/peers/create/configuration/types.ts | 5 + ui/app/peers/create/details/configForm.tsx | 44 ------- ui/app/peers/create/details/handlers.ts | 46 -------- ui/app/peers/create/details/helpers/pg.ts | 44 ------- ui/app/peers/create/details/page.tsx | 82 ------------- ui/app/peers/create/details/schema.ts | 9 -- ui/app/peers/create/details/types.ts | 5 - ui/app/peers/create/page.tsx | 38 ++++-- ui/app/peers/edit/[connectorId]/page.tsx | 4 +- ui/components/SelectSource.tsx | 54 +++++---- 19 files changed, 420 insertions(+), 280 deletions(-) create mode 100644 ui/app/api/peers/create/route.ts create mode 100644 ui/app/api/peers/peerTypeMap.ts create mode 100644 ui/app/api/peers/validate/route.ts delete mode 100644 ui/app/peers/create/api.ts create mode 100644 ui/app/peers/create/configuration/configForm.tsx create mode 100644 ui/app/peers/create/configuration/handlers.ts create mode 100644 ui/app/peers/create/configuration/helpers/pg.ts create mode 100644 ui/app/peers/create/configuration/page.tsx create mode 100644 ui/app/peers/create/configuration/schema.ts create mode 100644 ui/app/peers/create/configuration/types.ts delete mode 100644 ui/app/peers/create/details/configForm.tsx delete mode 100644 ui/app/peers/create/details/handlers.ts delete mode 100644 ui/app/peers/create/details/helpers/pg.ts delete mode 100644 ui/app/peers/create/details/page.tsx delete mode 100644 ui/app/peers/create/details/schema.ts delete mode 100644 ui/app/peers/create/details/types.ts diff --git a/ui/app/api/peers/create/route.ts b/ui/app/api/peers/create/route.ts new file mode 100644 index 0000000000..81880dcddf --- /dev/null +++ b/ui/app/api/peers/create/route.ts @@ -0,0 +1,29 @@ +import { + CreatePeerRequest, + CreatePeerResponse, + CreatePeerStatus, +} from '@/grpc_generated/route'; +import { GetFlowServiceClient } from '@/rpc/rpc'; +import { typeMap } from '../peerTypeMap'; + +export async function POST(request: Request) { + let body = await request.json(); + let name = body.name; + let type = body.type; + let config = body.config; + let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; + let flowServiceClient = GetFlowServiceClient(flowServiceAddress); + let req: CreatePeerRequest = { + peer: { + name: name, + type: typeMap(type), + postgresConfig: config, + }, + }; + let status: CreatePeerResponse = await flowServiceClient.createPeer(req); + if (status.status === CreatePeerStatus.FAILED) { + return new Response(status.message); + } else if (status.status === CreatePeerStatus.CREATED) { + return new Response('created'); + } else return new Response('status of peer creation is unknown'); +} diff --git a/ui/app/api/peers/peerTypeMap.ts b/ui/app/api/peers/peerTypeMap.ts new file mode 100644 index 0000000000..b8ed289029 --- /dev/null +++ b/ui/app/api/peers/peerTypeMap.ts @@ -0,0 +1,12 @@ +import { DBType } from '@/grpc_generated/peers'; + +export const typeMap = (type: string) => { + switch (type) { + case 'POSTGRES': + return DBType.POSTGRES; + case 'SNOWFLAKE': + return DBType.SNOWFLAKE; + default: + return DBType.UNRECOGNIZED; + } +}; diff --git a/ui/app/api/peers/validate/route.ts b/ui/app/api/peers/validate/route.ts new file mode 100644 index 0000000000..fdc1bf9af5 --- /dev/null +++ b/ui/app/api/peers/validate/route.ts @@ -0,0 +1,29 @@ +import { + ValidatePeerRequest, + ValidatePeerResponse, + ValidatePeerStatus, +} from '@/grpc_generated/route'; +import { GetFlowServiceClient } from '@/rpc/rpc'; +import { typeMap } from '../peerTypeMap'; + +export async function POST(request: Request) { + let body = await request.json(); + let name = body.name; + let type = body.type; + let config = body.config; + let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; + let flowServiceClient = GetFlowServiceClient(flowServiceAddress); + let req: ValidatePeerRequest = { + peer: { + name: name, + type: typeMap(type), + postgresConfig: config, + }, + }; + let status: ValidatePeerResponse = await flowServiceClient.validatePeer(req); + if (status.status === ValidatePeerStatus.INVALID) { + return new Response(status.message); + } else if (status.status === ValidatePeerStatus.VALID) { + return new Response('valid'); + } else return new Response('status of validate is unknown'); +} diff --git a/ui/app/peers/create/api.ts b/ui/app/peers/create/api.ts deleted file mode 100644 index 01bf545015..0000000000 --- a/ui/app/peers/create/api.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { CreatePeerRequest, ValidatePeerRequest } from '@/grpc_generated/route'; -import { GetFlowServiceClient } from '@/rpc/rpc'; - -export async function validatePeer(req: ValidatePeerRequest) { - let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - let flowServiceClient = GetFlowServiceClient(flowServiceAddress); -} - -export async function createPeer(req: CreatePeerRequest) { - let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - let flowServiceClient = GetFlowServiceClient(flowServiceAddress); -} \ No newline at end of file diff --git a/ui/app/peers/create/configuration/configForm.tsx b/ui/app/peers/create/configuration/configForm.tsx new file mode 100644 index 0000000000..1014c44cf5 --- /dev/null +++ b/ui/app/peers/create/configuration/configForm.tsx @@ -0,0 +1,40 @@ +'use client'; +import { Label } from '@/lib/Label'; +import { RowWithTextField } from '@/lib/Layout'; +import { TextField } from '@/lib/TextField'; +import { PeerSetter } from './types'; + +interface Setting { + label: string; + stateHandler: (value: string, setter: PeerSetter) => void; + type?: string; +} + +interface ConfigProps { + settings: Setting[]; + setter: PeerSetter; +} + +export default function PgConfig(props: ConfigProps) { + return ( + <> + {props.settings.map((setting, id) => { + return ( + {setting.label}} + action={ + + setting.stateHandler(e.target.value, props.setter) + } + /> + } + /> + ); + })} + + ); +} diff --git a/ui/app/peers/create/configuration/handlers.ts b/ui/app/peers/create/configuration/handlers.ts new file mode 100644 index 0000000000..a414e10d09 --- /dev/null +++ b/ui/app/peers/create/configuration/handlers.ts @@ -0,0 +1,83 @@ +import { Dispatch, SetStateAction } from 'react'; +import { checkFormFields } from './schema'; +import { PeerConfig } from './types'; + +// Frontend form validation +const validateFields = ( + type: string, + config: PeerConfig, + setMessage: Dispatch>, + name?: string +): boolean => { + if (!name) { + setMessage({ ok: false, msg: 'Peer name is required' }); + return false; + } + + const validity = checkFormFields(type, config); + if (validity.error) { + setMessage({ ok: false, msg: validity.error.message }); + return false; + } else setMessage({ ok: true, msg: '' }); + return true; +}; + +// API call to validate peer +export const handleValidate = async ( + type: string, + config: PeerConfig, + setMessage: Dispatch>, + setLoading: Dispatch>, + name?: string +) => { + const isValid = validateFields(type, config, setMessage, name); + if (!isValid) return; + setLoading(true); + const statusMessage = await fetch('/api/peers/validate', { + method: 'POST', + body: JSON.stringify({ + name, + type, + config, + }), + }).then((res) => res.text()); + + if (statusMessage !== 'valid') { + setMessage({ ok: false, msg: statusMessage }); + setLoading(false); + return; + } else { + setMessage({ ok: true, msg: 'Peer is valid' }); + } + setLoading(false); +}; + +// API call to create peer +export const handleCreate = async ( + type: string, + config: PeerConfig, + setMessage: Dispatch>, + setLoading: Dispatch>, + name?: string +) => { + let isValid = validateFields(type, config, setMessage, name); + if (!isValid) return; + setLoading(true); + const statusMessage = await fetch('/api/peers/create', { + method: 'POST', + body: JSON.stringify({ + name, + type, + config, + }), + }).then((res) => res.text()); + + if (statusMessage !== 'created') { + setMessage({ ok: false, msg: statusMessage }); + setLoading(false); + return; + } else { + setMessage({ ok: true, msg: 'Peer created successfully' }); + } + setLoading(false); +}; diff --git a/ui/app/peers/create/configuration/helpers/pg.ts b/ui/app/peers/create/configuration/helpers/pg.ts new file mode 100644 index 0000000000..cf005f2985 --- /dev/null +++ b/ui/app/peers/create/configuration/helpers/pg.ts @@ -0,0 +1,36 @@ +import { PeerSetter } from '../types'; + +export const postgresSetting = [ + { + label: 'Host', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, host: value })), + }, + { + label: 'Port', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, port: parseInt(value, 10) })), + type: 'number', // type for textfield + }, + { + label: 'User', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, user: value })), + }, + { + label: 'Password', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, password: value })), + type: 'password', + }, + { + label: 'Database', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, database: value })), + }, + { + label: 'Transaction Snapshot', + stateHandler: (value: string, setter: PeerSetter) => + setter((curr) => ({ ...curr, transactionSnapshot: value })), + }, +]; diff --git a/ui/app/peers/create/configuration/page.tsx b/ui/app/peers/create/configuration/page.tsx new file mode 100644 index 0000000000..6432e8695b --- /dev/null +++ b/ui/app/peers/create/configuration/page.tsx @@ -0,0 +1,108 @@ +'use client'; +import { Button } from '@/lib/Button'; +import { ButtonGroup } from '@/lib/ButtonGroup'; +import { Label } from '@/lib/Label'; +import { LayoutMain, RowWithTextField } from '@/lib/Layout'; +import { Panel } from '@/lib/Panel'; +import { TextField } from '@/lib/TextField'; +import Link from 'next/link'; +import { useSearchParams } from 'next/navigation'; +import { useState } from 'react'; +import PgConfig from './configForm'; +import { handleCreate, handleValidate } from './handlers'; +import { postgresSetting } from './helpers/pg'; +import { PeerConfig } from './types'; + +export default function CreateConfig() { + const searchParams = useSearchParams(); + const dbType = searchParams.get('dbtype') || ''; + const [name, setName] = useState(''); + const [config, setConfig] = useState({ + host: '', + port: 5432, + user: '', + password: '', + database: '', + transactionSnapshot: '', + }); + const [formMessage, setFormMessage] = useState<{ ok: boolean; msg: string }>({ + ok: true, + msg: '', + }); + const [loading, setLoading] = useState(false); + const configComponentMap = (dbType: string) => { + switch (dbType) { + case 'POSTGRES': + return ; + default: + return <>; + } + }; + + return ( + + + + + + + + Name} + action={ + setName(e.target.value || '')} + /> + } + /> + {dbType && configComponentMap(dbType)} + + + + + + + + + {loading && ( + + )} + {!loading && formMessage.msg.length > 0 && ( + + )} + + + + ); +} diff --git a/ui/app/peers/create/configuration/schema.ts b/ui/app/peers/create/configuration/schema.ts new file mode 100644 index 0000000000..f16035cfcb --- /dev/null +++ b/ui/app/peers/create/configuration/schema.ts @@ -0,0 +1,20 @@ +import Joi from 'joi'; +import { PeerConfig } from './types'; + +export const pgSchema = Joi.object({ + host: Joi.string().required(), + port: Joi.number().integer().min(1).required(), + database: Joi.string().min(1).max(100).required(), + user: Joi.string().alphanum().min(1).max(64).required(), + password: Joi.string().min(1).max(100).required(), + transactionSnapshot: Joi.string().max(100).allow('').optional(), +}); + +export const checkFormFields = (peerType: string, config: PeerConfig) => { + switch (peerType) { + case 'POSTGRES': + return pgSchema.validate(config); + default: + return { error: { message: 'Peer type not recognized' } }; + } +}; diff --git a/ui/app/peers/create/configuration/types.ts b/ui/app/peers/create/configuration/types.ts new file mode 100644 index 0000000000..7e678151e6 --- /dev/null +++ b/ui/app/peers/create/configuration/types.ts @@ -0,0 +1,5 @@ +import { PostgresConfig } from '@/grpc_generated/peers'; +import { Dispatch, SetStateAction } from 'react'; + +export type PeerConfig = PostgresConfig; +export type PeerSetter = Dispatch>; diff --git a/ui/app/peers/create/details/configForm.tsx b/ui/app/peers/create/details/configForm.tsx deleted file mode 100644 index 992f91ccde..0000000000 --- a/ui/app/peers/create/details/configForm.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client" -import { Label } from '@/lib/Label'; -import { RowWithTextField } from '@/lib/Layout'; -import { TextField } from '@/lib/TextField'; -import { Dispatch, SetStateAction } from 'react'; -import { PeerConfig, PeerSetter } from './types'; - -interface Field { - label: string, - type?: string -} - -interface ConfigData{ - fields: Field[] - handler: (fieldType: string, value:string, - setter:PeerSetter) => void -} - -interface ConfigProps{ - configData: ConfigData - setter: PeerSetter -} - -export default function PgConfig(props: ConfigProps) { - return( - <> - {props.configData.fields.map((field) => { - return( - - {field.label} - - } - action={ props.configData.handler(field.label, e.target.value, props.setter) - }/>} - /> - ) - }) - } - - ) -} \ No newline at end of file diff --git a/ui/app/peers/create/details/handlers.ts b/ui/app/peers/create/details/handlers.ts deleted file mode 100644 index 2286655961..0000000000 --- a/ui/app/peers/create/details/handlers.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { DBType } from "@/grpc_generated/peers"; -import { ValidatePeerRequest } from "@/grpc_generated/route"; -import { GetFlowServiceClient } from "@/rpc/rpc"; -import { PeerConfig } from "./types"; -import { pgSchema } from "./schema"; -import { Dispatch, SetStateAction } from "react"; - -const typeMap = (type:string)=> { - switch(type){ - case "POSTGRES": return DBType.POSTGRES - case "SNOWFLAKE": return DBType.SNOWFLAKE - default: return DBType.UNRECOGNIZED - } -} -export const handleValidate = async ( - type:string, - config:PeerConfig, - setErr:Dispatch>, - name?:string - ) => { - if (!name){ - setErr("Peer name is required"); - return; - } - const validity = pgSchema.validate(config); - if(validity.error){ - setErr(validity.error.message); - return; - } - let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - let flowServiceClient = GetFlowServiceClient(flowServiceAddress); - let req: ValidatePeerRequest = { - peer:{ - "name": name, - "type": typeMap(type), - "postgresConfig": config - } - }; - let peers = await flowServiceClient.listPeers(req); - return peers.peers; -} - - -const handleCreate = () => { - -} \ No newline at end of file diff --git a/ui/app/peers/create/details/helpers/pg.ts b/ui/app/peers/create/details/helpers/pg.ts deleted file mode 100644 index d7a8007cb8..0000000000 --- a/ui/app/peers/create/details/helpers/pg.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Dispatch, SetStateAction } from 'react'; -import { PeerConfig, PeerSetter } from '../types'; - -const pgFields = [{ - label: "Host" -},{ - label: "Port", - type: "number" // type for textfield -},{ - label: "User" -},{ - label: "Password", - type: "password" -},{ - label: "Database", -}] - -const pgHandler = (fieldType: string, value:string, - setter: PeerSetter) => { - switch(fieldType){ - case "Host": - setter(curr => ({...curr, host:value})) - break; - case "Port": - setter(curr => ({...curr, port:parseInt(value, 10)})) - break; - case "User": - setter(curr => ({...curr, user:value})) - break; - case "Password": - setter(curr => ({...curr, password:value})) - break; - case "Database": - setter(curr => ({...curr, database:value})) - break; - default: - break; - } -} - -export const pgConfigData = { - fields: pgFields, - handler: pgHandler -} diff --git a/ui/app/peers/create/details/page.tsx b/ui/app/peers/create/details/page.tsx deleted file mode 100644 index 458ad8c6cd..0000000000 --- a/ui/app/peers/create/details/page.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client" -import { Label } from '@/lib/Label'; -import { LayoutMain } from '@/lib/Layout'; -import { Button } from '@/lib/Button'; -import { ButtonGroup } from '@/lib/ButtonGroup'; -import { Panel } from '@/lib/Panel'; -import { RowWithTextField } from '@/lib/Layout'; -import { TextField } from '@/lib/TextField'; -import { useSearchParams } from 'next/navigation'; -import Link from 'next/link'; -import PgConfig from './configForm'; -import { useState } from 'react'; -import { pgConfigData } from './helpers/pg'; -import { PeerConfig } from './types'; -import { handleValidate } from './handlers'; - -export default function CreateConfig() { - const searchParams = useSearchParams(); - const dbType = searchParams.get('dbtype')||""; - const [name,setName] = useState(""); - const [config, setConfig] = useState({ - host:"", - port:5432, - user:"", - password:"", - database:"", - transactionSnapshot:"" - }) - const [formErr, setFormErr] = useState("") - const configComponentMap = (dbType:string) => { - switch(dbType){ - case "POSTGRES": - return - default: - return <> - } - } - - return( - - - - - - - - - Name - - } - action={setName(e.currentTarget.textContent||"")}/>} - /> - { - dbType && configComponentMap(dbType) - } - - - - - - - - - - - - - ) -} diff --git a/ui/app/peers/create/details/schema.ts b/ui/app/peers/create/details/schema.ts deleted file mode 100644 index afa478ca6a..0000000000 --- a/ui/app/peers/create/details/schema.ts +++ /dev/null @@ -1,9 +0,0 @@ -import Joi from 'joi'; - -export const pgSchema = Joi.object({ - host:Joi.string().required(), - port:Joi.number().integer().min(1).required(), - database:Joi.string().min(1).max(100).required(), - user:Joi.string().alphanum().min(1).max(64).required(), - password:Joi.string().min(1).max(100).required(), -}); \ No newline at end of file diff --git a/ui/app/peers/create/details/types.ts b/ui/app/peers/create/details/types.ts deleted file mode 100644 index 3b9f6dc7b0..0000000000 --- a/ui/app/peers/create/details/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PostgresConfig } from "@/grpc_generated/peers"; -import { Dispatch, SetStateAction } from 'react'; - -export type PeerConfig = PostgresConfig -export type PeerSetter = Dispatch> \ No newline at end of file diff --git a/ui/app/peers/create/page.tsx b/ui/app/peers/create/page.tsx index 156078a64c..95857bbe34 100644 --- a/ui/app/peers/create/page.tsx +++ b/ui/app/peers/create/page.tsx @@ -1,4 +1,4 @@ -"use client" +'use client'; import { Action } from '@/lib/Action'; import { Button } from '@/lib/Button'; import { ButtonGroup } from '@/lib/ButtonGroup'; @@ -6,13 +6,13 @@ import { Icon } from '@/lib/Icon'; import { Label } from '@/lib/Label'; import { LayoutMain, RowWithSelect } from '@/lib/Layout'; import { Panel } from '@/lib/Panel'; -import SelectSource from '../../../components/SelectSource'; -import { useState } from 'react'; -import { useRouter } from 'next/navigation' import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import SelectSource from '../../../components/SelectSource'; export default function CreatePeer() { - const [peerType, setPeerType] = useState("") + const [peerType, setPeerType] = useState(''); const router = useRouter(); return ( @@ -21,7 +21,13 @@ export default function CreatePeer() { - } href='https://docs.peerdb.io/sql/commands/create-peer' target='_blank'>Learn about peers + } + href='https://docs.peerdb.io/sql/commands/create-peer' + target='_blank' + > + Learn about peers + } - action={} + action={ + + } /> - - + + diff --git a/ui/app/peers/edit/[connectorId]/page.tsx b/ui/app/peers/edit/[connectorId]/page.tsx index 7593771a5c..232a3f77ed 100644 --- a/ui/app/peers/edit/[connectorId]/page.tsx +++ b/ui/app/peers/edit/[connectorId]/page.tsx @@ -145,9 +145,7 @@ const ExampleTable = ({ title }: { title: string }) => ( type EditMirrorProps = { params: { peerId: string }; }; -export default function EditPeer({ - params: { peerId }, -}: EditMirrorProps) { +export default function EditPeer({ params: { peerId } }: EditMirrorProps) { const [logsOpen, setLogsOpen] = useState(false); return ( <> diff --git a/ui/components/SelectSource.tsx b/ui/components/SelectSource.tsx index 0150636b3b..89f59d8e8b 100644 --- a/ui/components/SelectSource.tsx +++ b/ui/components/SelectSource.tsx @@ -1,29 +1,35 @@ -"use client" -import { Select, SelectItem } from '@/lib/Select'; +'use client'; import { DBType } from '@/grpc_generated/peers'; +import { Select, SelectItem } from '@/lib/Select'; import { Dispatch, SetStateAction } from 'react'; -interface SelectSourceProps{ - peerType: string; - setPeerType: Dispatch> +interface SelectSourceProps { + peerType: string; + setPeerType: Dispatch>; } -export default function SelectSource({peerType,setPeerType}:SelectSourceProps){ - const dbTypes: string[] = Object.values(DBType) - .filter((value): value is string => typeof value === 'string' - && value !== 'UNRECOGNIZED' - && value !== 'MONGO' - ); - - return( - setPeerType(val)} + > + {dbTypes.map((dbType, id) => { + return ( + + {dbType} + + ); + })} + + ); +} From d2d08f771eec84da0e30e023eefa256c1d51705a Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Fri, 29 Sep 2023 20:24:09 +0530 Subject: [PATCH 05/12] routes back to list --- ui/app/peers/create/configuration/handlers.ts | 3 +++ ui/app/peers/create/configuration/page.tsx | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ui/app/peers/create/configuration/handlers.ts b/ui/app/peers/create/configuration/handlers.ts index a414e10d09..db978f1552 100644 --- a/ui/app/peers/create/configuration/handlers.ts +++ b/ui/app/peers/create/configuration/handlers.ts @@ -1,3 +1,4 @@ +import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context'; import { Dispatch, SetStateAction } from 'react'; import { checkFormFields } from './schema'; import { PeerConfig } from './types'; @@ -58,6 +59,7 @@ export const handleCreate = async ( config: PeerConfig, setMessage: Dispatch>, setLoading: Dispatch>, + router: AppRouterInstance, name?: string ) => { let isValid = validateFields(type, config, setMessage, name); @@ -78,6 +80,7 @@ export const handleCreate = async ( return; } else { setMessage({ ok: true, msg: 'Peer created successfully' }); + router.push('/peers'); } setLoading(false); }; diff --git a/ui/app/peers/create/configuration/page.tsx b/ui/app/peers/create/configuration/page.tsx index 6432e8695b..e769e0448c 100644 --- a/ui/app/peers/create/configuration/page.tsx +++ b/ui/app/peers/create/configuration/page.tsx @@ -6,15 +6,15 @@ import { LayoutMain, RowWithTextField } from '@/lib/Layout'; import { Panel } from '@/lib/Panel'; import { TextField } from '@/lib/TextField'; import Link from 'next/link'; -import { useSearchParams } from 'next/navigation'; +import { useRouter, useSearchParams } from 'next/navigation'; import { useState } from 'react'; import PgConfig from './configForm'; import { handleCreate, handleValidate } from './handlers'; import { postgresSetting } from './helpers/pg'; import { PeerConfig } from './types'; - export default function CreateConfig() { const searchParams = useSearchParams(); + const router = useRouter(); const dbType = searchParams.get('dbtype') || ''; const [name, setName] = useState(''); const [config, setConfig] = useState({ @@ -76,7 +76,14 @@ export default function CreateConfig() { - + + + diff --git a/ui/app/peers/page.tsx b/ui/app/peers/page.tsx index 55a83a0aa4..9951c55b4f 100644 --- a/ui/app/peers/page.tsx +++ b/ui/app/peers/page.tsx @@ -9,15 +9,14 @@ import { Panel } from '@/lib/Panel'; import { SearchField } from '@/lib/SearchField'; import { Select } from '@/lib/Select'; import { Table, TableCell, TableRow } from '@/lib/Table'; -import { GetFlowServiceClient } from '@/rpc/rpc'; +import { GetFlowServiceClientFromEnv } from '@/rpc/rpc'; import Link from 'next/link'; import { Suspense } from 'react'; import { Header } from '../../lib/Header'; export const dynamic = 'force-dynamic'; async function fetchPeers() { - let flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - let flowServiceClient = GetFlowServiceClient(flowServiceAddress); + let flowServiceClient = GetFlowServiceClientFromEnv(); let req: ListPeersRequest = {}; let peers = await flowServiceClient.listPeers(req); return peers.peers; diff --git a/ui/package.json b/ui/package.json index 04caffea75..256eb9cd70 100644 --- a/ui/package.json +++ b/ui/package.json @@ -31,7 +31,6 @@ "@types/react": "18.2.21", "@types/react-dom": "18.2.7", "classnames": "^2.3.2", - "joi": "^17.10.2", "long": "^5.2.3", "material-symbols": "0.11.0", "next": "13.4.16", @@ -39,7 +38,8 @@ "protobufjs": "^7.2.5", "react": "18.2.0", "react-dom": "18.2.0", - "styled-components": "^6.0.7" + "styled-components": "^6.0.7", + "zod": "^3.22.2" }, "devDependencies": { "@storybook/addon-essentials": "^7.3.0", diff --git a/ui/rpc/rpc.ts b/ui/rpc/rpc.ts index 1c344ae81a..e2f014d6b8 100644 --- a/ui/rpc/rpc.ts +++ b/ui/rpc/rpc.ts @@ -4,7 +4,8 @@ import { FlowServiceClient } from '@/grpc_generated/route'; import { credentials } from '@grpc/grpc-js'; import { promisifyClient } from './promisify'; -export function GetFlowServiceClient(address: string) { +export function GetFlowServiceClientFromEnv() { + let address = process.env.PEERDB_FLOW_SERVER_ADDRESS!; console.log(`Connecting to Flow server at ${address}`); return promisifyClient( new FlowServiceClient(address, credentials.createInsecure()) diff --git a/ui/yarn.lock b/ui/yarn.lock index 79f71c12e4..bfb834a654 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -1410,18 +1410,6 @@ protobufjs "^7.2.4" yargs "^17.7.2" -"@hapi/hoek@^9.0.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" - integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== - -"@hapi/topo@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" - integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== - dependencies: - "@hapi/hoek" "^9.0.0" - "@headlessui/react@^1.7.14": version "1.7.17" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.17.tgz#a0ec23af21b527c030967245fd99776aa7352bc6" @@ -2189,23 +2177,6 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz#16ab6c727d8c2020a5b6e4a176a243ecd88d8d69" integrity sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw== -"@sideway/address@^4.1.3": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" - integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw== - dependencies: - "@hapi/hoek" "^9.0.0" - -"@sideway/formula@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" - integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== - -"@sideway/pinpoint@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" - integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== - "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -7325,17 +7296,6 @@ jiti@^1.18.2: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569" integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w== -joi@^17.10.2: - version "17.10.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.10.2.tgz#4ecc348aa89ede0b48335aad172e0f5591e55b29" - integrity sha512-hcVhjBxRNW/is3nNLdGLIjkgXetkeGc2wyhydhz8KumG23Aerk4HPjU5zaPAMRqXQFc0xNqXTC7+zQjxr0GlKA== - dependencies: - "@hapi/hoek" "^9.0.0" - "@hapi/topo" "^5.0.0" - "@sideway/address" "^4.1.3" - "@sideway/formula" "^3.0.1" - "@sideway/pinpoint" "^2.0.0" - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -10797,3 +10757,8 @@ zod@3.21.4: version "3.21.4" resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== + +zod@^3.22.2: + version "3.22.2" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" + integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg== From 5580b73bfc104d8575283e9f766e70904d066912 Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Tue, 3 Oct 2023 20:31:43 +0530 Subject: [PATCH 11/12] fix: form errors --- ui/app/api/peers/route.ts | 2 +- ui/app/peers/create/configuration/handlers.ts | 16 +++-- ui/app/peers/create/configuration/schema.ts | 58 +++++++++++++------ 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/ui/app/api/peers/route.ts b/ui/app/api/peers/route.ts index 1f8704849c..3fbba1b902 100644 --- a/ui/app/api/peers/route.ts +++ b/ui/app/api/peers/route.ts @@ -10,7 +10,7 @@ import { } from '@/grpc_generated/route'; import { GetFlowServiceClientFromEnv } from '@/rpc/rpc'; -export const constructPeer = ( +const constructPeer = ( name: string, type: string, config: PeerConfig diff --git a/ui/app/peers/create/configuration/handlers.ts b/ui/app/peers/create/configuration/handlers.ts index 6b40874e3c..133c62d6e5 100644 --- a/ui/app/peers/create/configuration/handlers.ts +++ b/ui/app/peers/create/configuration/handlers.ts @@ -1,6 +1,6 @@ import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context'; import { Dispatch, SetStateAction } from 'react'; -import { checkFormFields } from './schema'; +import { pgSchema } from './schema'; import { PeerConfig } from './types'; // Frontend form validation @@ -14,9 +14,17 @@ const validateFields = ( setMessage({ ok: false, msg: 'Peer name is required' }); return false; } - const validity = checkFormFields(type, config); - if (validity.success === false) { - setMessage({ ok: false, msg: validity.error.message }); + let validationErr: string | undefined; + switch (type) { + case 'POSTGRES': + const pgConfig = pgSchema.safeParse(config); + if (!pgConfig.success) validationErr = pgConfig.error.issues[0].message; + break; + default: + validationErr = 'Unsupported peer type ' + type; + } + if (validationErr) { + setMessage({ ok: false, msg: validationErr }); return false; } else setMessage({ ok: true, msg: '' }); return true; diff --git a/ui/app/peers/create/configuration/schema.ts b/ui/app/peers/create/configuration/schema.ts index 935627b2c7..05e0dcaeb5 100644 --- a/ui/app/peers/create/configuration/schema.ts +++ b/ui/app/peers/create/configuration/schema.ts @@ -1,20 +1,44 @@ import * as z from 'zod'; -import { PeerConfig } from './types'; -const pgSchema = z.object({ - host: z.string().nonempty().max(255), - port: z.number().int().min(1).max(65535), - database: z.string().min(1).max(100), - user: z.string().min(1).max(64), - password: z.string().min(1).max(100), - transactionSnapshot: z.string().max(100).optional(), +export const pgSchema = z.object({ + host: z + .string({ + required_error: 'Host is required', + invalid_type_error: 'Host must be a string', + }) + .nonempty() + .max(255, 'Host must be less than 255 characters'), + port: z + .number({ + required_error: 'Port is required', + invalid_type_error: 'Port must be a number', + }) + .int() + .min(1, 'Port must be a positive integer') + .max(65535, 'Port must be below 65535'), + database: z + .string({ + required_error: 'Database is required', + invalid_type_error: 'Database must be a string', + }) + .min(1, { message: 'Database name should be non-empty' }) + .max(100, 'Database must be less than 100 characters'), + user: z + .string({ + required_error: 'User is required', + invalid_type_error: 'User must be a string', + }) + .min(1, 'User must be non-empty') + .max(64, 'User must be less than 64 characters'), + password: z + .string({ + required_error: 'Password is required', + invalid_type_error: 'Password must be a string', + }) + .min(1, 'Password must be non-empty') + .max(100, 'Password must be less than 100 characters'), + transactionSnapshot: z + .string() + .max(100, 'Transaction snapshot too long (100 char limit)') + .optional(), }); - -export const checkFormFields = (peerType: string, config: PeerConfig) => { - switch (peerType) { - case 'POSTGRES': - return pgSchema.safeParse(config); - default: - return { success: false, error: { message: 'Peer type not recognized' } }; - } -}; From 947992e6b7825d94831beb2a8fde3b1d12939cd9 Mon Sep 17 00:00:00 2001 From: Amogh-Bharadwaj Date: Tue, 3 Oct 2023 20:34:32 +0530 Subject: [PATCH 12/12] fix: name of config component --- ui/app/peers/create/configuration/configForm.tsx | 2 +- ui/app/peers/create/configuration/page.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/app/peers/create/configuration/configForm.tsx b/ui/app/peers/create/configuration/configForm.tsx index 1014c44cf5..de47e1764d 100644 --- a/ui/app/peers/create/configuration/configForm.tsx +++ b/ui/app/peers/create/configuration/configForm.tsx @@ -15,7 +15,7 @@ interface ConfigProps { setter: PeerSetter; } -export default function PgConfig(props: ConfigProps) { +export default function ConfigForm(props: ConfigProps) { return ( <> {props.settings.map((setting, id) => { diff --git a/ui/app/peers/create/configuration/page.tsx b/ui/app/peers/create/configuration/page.tsx index b5873ded9f..b8ce42c746 100644 --- a/ui/app/peers/create/configuration/page.tsx +++ b/ui/app/peers/create/configuration/page.tsx @@ -8,7 +8,7 @@ import { TextField } from '@/lib/TextField'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { useState } from 'react'; -import PgConfig from './configForm'; +import ConfigForm from './configForm'; import { handleCreate, handleValidate } from './handlers'; import { getBlankSetting } from './helpers/common'; import { postgresSetting } from './helpers/pg'; @@ -28,7 +28,7 @@ export default function CreateConfig() { const configComponentMap = (dbType: string) => { switch (dbType) { case 'POSTGRES': - return ; + return ; default: return <>; }