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==