diff --git a/ui/app/api/peers/create/route.ts b/ui/app/api/peers/create/route.ts deleted file mode 100644 index 1930c3ee31..0000000000 --- a/ui/app/api/peers/create/route.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - CreatePeerRequest, - CreatePeerResponse, - CreatePeerStatus, -} from '@/grpc_generated/route'; -import { GetFlowServiceClient } from '@/rpc/rpc'; -import { constructPeer } from '../util'; - -export async function POST(request: Request) { - const body = await request.json(); - const { name, type, config } = body; - const flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - const flowServiceClient = GetFlowServiceClient(flowServiceAddress); - const peer = constructPeer(name, type, config); - const req: CreatePeerRequest = { peer }; - const 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/route.ts b/ui/app/api/peers/route.ts new file mode 100644 index 0000000000..1f8704849c --- /dev/null +++ b/ui/app/api/peers/route.ts @@ -0,0 +1,54 @@ +import { PeerConfig } from '@/app/peers/create/configuration/types'; +import { DBType, Peer, PostgresConfig } from '@/grpc_generated/peers'; +import { + CreatePeerRequest, + CreatePeerResponse, + CreatePeerStatus, + ValidatePeerRequest, + ValidatePeerResponse, + ValidatePeerStatus, +} from '@/grpc_generated/route'; +import { GetFlowServiceClientFromEnv } from '@/rpc/rpc'; + +export const constructPeer = ( + name: string, + type: string, + config: PeerConfig +): Peer | undefined => { + switch (type) { + case 'POSTGRES': + return { + name, + type: DBType.POSTGRES, + postgresConfig: config as PostgresConfig, + }; + default: + return; + } +}; + +export async function POST(request: Request) { + const body = await request.json(); + const { name, type, config, mode } = body; + const flowServiceClient = GetFlowServiceClientFromEnv(); + const peer = constructPeer(name, type, config); + if (mode === 'validate') { + const validateReq: ValidatePeerRequest = { peer }; + const validateStatus: ValidatePeerResponse = + await flowServiceClient.validatePeer(validateReq); + if (validateStatus.status === ValidatePeerStatus.INVALID) { + return new Response(validateStatus.message); + } else if (validateStatus.status === ValidatePeerStatus.VALID) { + return new Response('valid'); + } + } else if (mode === 'create') { + const req: CreatePeerRequest = { peer }; + const createStatus: CreatePeerResponse = + await flowServiceClient.createPeer(req); + if (createStatus.status === CreatePeerStatus.FAILED) { + return new Response(createStatus.message); + } else if (createStatus.status === CreatePeerStatus.CREATED) { + return new Response('created'); + } else return new Response('status of peer creation is unknown'); + } else return new Response('mode of peer creation is unknown'); +} diff --git a/ui/app/api/peers/util.ts b/ui/app/api/peers/util.ts deleted file mode 100644 index f49f9ebacb..0000000000 --- a/ui/app/api/peers/util.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { PeerConfig } from '@/app/peers/create/configuration/types'; -import { DBType, Peer, PostgresConfig } from '@/grpc_generated/peers'; - -export const constructPeer = ( - name: string, - type: string, - config: PeerConfig -): Peer | undefined => { - switch (type) { - case 'POSTGRES': - return { - name, - type: DBType.POSTGRES, - postgresConfig: config as PostgresConfig, - }; - default: - return; - } -}; diff --git a/ui/app/api/peers/validate/route.ts b/ui/app/api/peers/validate/route.ts deleted file mode 100644 index 61f0329a1a..0000000000 --- a/ui/app/api/peers/validate/route.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - ValidatePeerRequest, - ValidatePeerResponse, - ValidatePeerStatus, -} from '@/grpc_generated/route'; -import { GetFlowServiceClient } from '@/rpc/rpc'; -import { constructPeer } from '../util'; - -export async function POST(request: Request) { - const body = await request.json(); - const { name, type, config } = body; - const flowServiceAddress = process.env.PEERDB_FLOW_SERVER_ADDRESS!; - const flowServiceClient = GetFlowServiceClient(flowServiceAddress); - const peer = constructPeer(name, type, config); - const req: ValidatePeerRequest = { peer }; - const 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/configuration/handlers.ts b/ui/app/peers/create/configuration/handlers.ts index e7412d2a6e..6b40874e3c 100644 --- a/ui/app/peers/create/configuration/handlers.ts +++ b/ui/app/peers/create/configuration/handlers.ts @@ -15,7 +15,7 @@ const validateFields = ( return false; } const validity = checkFormFields(type, config); - if (validity.error) { + if (validity.success === false) { setMessage({ ok: false, msg: validity.error.message }); return false; } else setMessage({ ok: true, msg: '' }); @@ -33,12 +33,13 @@ export const handleValidate = async ( const isValid = validateFields(type, config, setMessage, name); if (!isValid) return; setLoading(true); - const statusMessage = await fetch('/api/peers/validate', { + const statusMessage = await fetch('/api/peers/', { method: 'POST', body: JSON.stringify({ name, type, config, + mode: 'validate', }), }).then((res) => res.text()); if (statusMessage !== 'valid') { @@ -63,12 +64,13 @@ export const handleCreate = async ( let isValid = validateFields(type, config, setMessage, name); if (!isValid) return; setLoading(true); - const statusMessage = await fetch('/api/peers/create', { + const statusMessage = await fetch('/api/peers/', { method: 'POST', body: JSON.stringify({ name, type, config, + mode: 'create', }), }).then((res) => res.text()); if (statusMessage !== 'created') { diff --git a/ui/app/peers/create/configuration/schema.ts b/ui/app/peers/create/configuration/schema.ts index 075a3bf125..935627b2c7 100644 --- a/ui/app/peers/create/configuration/schema.ts +++ b/ui/app/peers/create/configuration/schema.ts @@ -1,20 +1,20 @@ -import Joi from 'joi'; +import * as z from 'zod'; import { PeerConfig } from './types'; -export const pgSchema = Joi.object({ - host: Joi.string().required(), - port: Joi.number().integer().min(1).max(65535).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(), +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 checkFormFields = (peerType: string, config: PeerConfig) => { switch (peerType) { case 'POSTGRES': - return pgSchema.validate(config); + return pgSchema.safeParse(config); default: - return { error: { message: 'Peer type not recognized' } }; + return { success: false, error: { message: 'Peer type not recognized' } }; } }; diff --git a/ui/app/peers/create/page.tsx b/ui/app/peers/create/page.tsx index d8c21e332b..63352337ba 100644 --- a/ui/app/peers/create/page.tsx +++ b/ui/app/peers/create/page.tsx @@ -46,15 +46,11 @@ export default function CreatePeer() { - + + + 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==