fix: fix domain schema check problem in add website server

and change add website / get website in website list request to trpc
This commit is contained in:
moonrailgun 2024-01-03 15:37:28 +08:00
parent 824bd89ede
commit a04aa67f85
6 changed files with 97 additions and 52 deletions

View File

@ -1,9 +1,8 @@
import { Button, Form, Input, Typography } from 'antd'; import { Button, Form, Input, Typography } from 'antd';
import React from 'react'; import React from 'react';
import { slugRegex } from '../../../../shared';
import { z } from 'zod';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { MonitorPicker } from '../MonitorPicker'; import { MonitorPicker } from '../MonitorPicker';
import { urlSlugValidator } from '../../../utils/validator';
const { Text } = Typography; const { Text } = Typography;
@ -62,14 +61,7 @@ export const MonitorStatusPageEditForm: React.FC<MonitorStatusPageEditFormProps>
required: true, required: true,
}, },
{ {
validator(rule, value, callback) { validator: urlSlugValidator,
try {
z.string().regex(slugRegex).parse(value);
callback();
} catch (err) {
callback('Not valid slug');
}
},
}, },
]} ]}
> >

View File

@ -1,8 +1,7 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import React from 'react'; import React from 'react';
import { MonitorProvider } from './types'; import { MonitorProvider } from './types';
import { z } from 'zod'; import { hostnameValidator } from '../../../utils/validator';
import { hostnameRegex } from '../../../../shared';
export const MonitorPing: React.FC = React.memo(() => { export const MonitorPing: React.FC = React.memo(() => {
return ( return (
@ -13,17 +12,7 @@ export const MonitorPing: React.FC = React.memo(() => {
rules={[ rules={[
{ required: true }, { required: true },
{ {
validator(rule, value, callback) { validator: hostnameValidator,
try {
z.union([
z.string().ip(),
z.string().regex(hostnameRegex),
]).parse(value);
callback();
} catch (err) {
callback('Not valid host, it should be ip or hostname');
}
},
}, },
]} ]}
> >

View File

@ -16,6 +16,7 @@ import {
} from '../../api/trpc'; } from '../../api/trpc';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useEvent } from '../../hooks/useEvent'; import { useEvent } from '../../hooks/useEvent';
import { hostnameValidator } from '../../utils/validator';
export const WebsiteInfo: React.FC = React.memo(() => { export const WebsiteInfo: React.FC = React.memo(() => {
const workspaceId = useCurrentWorkspaceId(); const workspaceId = useCurrentWorkspaceId();
@ -102,7 +103,12 @@ export const WebsiteInfo: React.FC = React.memo(() => {
<Form.Item <Form.Item
label="Domain" label="Domain"
name="domain" name="domain"
rules={[{ required: true }]} rules={[
{ required: true },
{
validator: hostnameValidator,
},
]}
> >
<Input size="large" /> <Input size="large" />
</Form.Item> </Form.Item>

View File

@ -7,43 +7,40 @@ import {
import { Button, Form, Input, Modal, Table, Typography } from 'antd'; import { Button, Form, Input, Modal, Table, Typography } from 'antd';
import { ColumnsType } from 'antd/es/table'; import { ColumnsType } from 'antd/es/table';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { import { WebsiteInfo } from '../../api/model/website';
addWorkspaceWebsite,
refreshWorkspaceWebsites,
useWorspaceWebsites,
WebsiteInfo,
} from '../../api/model/website';
import { Loading } from '../Loading'; import { Loading } from '../Loading';
import { NoWorkspaceTip } from '../NoWorkspaceTip'; import { useCurrentWorkspaceId } from '../../store/user';
import { useRequest } from '../../hooks/useRequest';
import { useUserStore } from '../../store/user';
import { useEvent } from '../../hooks/useEvent'; import { useEvent } from '../../hooks/useEvent';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { PageHeader } from '../PageHeader'; import { PageHeader } from '../PageHeader';
import { ModalButton } from '../ModalButton'; import { ModalButton } from '../ModalButton';
import { hostnameValidator } from '../../utils/validator';
import { trpc } from '../../api/trpc';
export const WebsiteList: React.FC = React.memo(() => { export const WebsiteList: React.FC = React.memo(() => {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const currentWorkspace = useUserStore( const workspaceId = useCurrentWorkspaceId();
(state) => state.info?.currentWorkspace
);
const [form] = Form.useForm(); const [form] = Form.useForm();
const addWebsiteMutation = trpc.website.add.useMutation();
const utils = trpc.useContext();
const [{ loading }, handleAddWebsite] = useRequest(async () => { const handleAddWebsite = useEvent(async () => {
await form.validateFields(); await form.validateFields();
const values = form.getFieldsValue(); const values = form.getFieldsValue();
await addWorkspaceWebsite(currentWorkspace!.id, values.name, values.domain); await addWebsiteMutation.mutateAsync({
refreshWorkspaceWebsites(currentWorkspace!.id); workspaceId,
name: values.name,
domain: values.domain,
});
utils.website.all.refetch();
setIsModalOpen(false); setIsModalOpen(false);
form.resetFields(); form.resetFields();
}); });
if (!currentWorkspace) {
return <NoWorkspaceTip />;
}
return ( return (
<div> <div>
<PageHeader <PageHeader
@ -62,13 +59,13 @@ export const WebsiteList: React.FC = React.memo(() => {
} }
/> />
<WebsiteListTable workspaceId={currentWorkspace.id} /> <WebsiteListTable workspaceId={workspaceId} />
<Modal <Modal
title="Add Server" title="Add Server"
open={isModalOpen} open={isModalOpen}
okButtonProps={{ okButtonProps={{
loading, loading: addWebsiteMutation.isLoading,
}} }}
onOk={() => handleAddWebsite()} onOk={() => handleAddWebsite()}
onCancel={() => setIsModalOpen(false)} onCancel={() => setIsModalOpen(false)}
@ -86,7 +83,12 @@ export const WebsiteList: React.FC = React.memo(() => {
label="Domain" label="Domain"
name="domain" name="domain"
tooltip="Your server domain, or ip." tooltip="Your server domain, or ip."
rules={[{ required: true }]} rules={[
{ required: true },
{
validator: hostnameValidator,
},
]}
> >
<Input /> <Input />
</Form.Item> </Form.Item>
@ -99,7 +101,9 @@ WebsiteList.displayName = 'WebsiteList';
const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo( const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
(props) => { (props) => {
const { websites, isLoading } = useWorspaceWebsites(props.workspaceId); const { data: websites = [], isLoading } = trpc.website.all.useQuery({
workspaceId: props.workspaceId,
});
const navigate = useNavigate(); const navigate = useNavigate();
const handleEdit = useEvent((websiteId) => { const handleEdit = useEvent((websiteId) => {

View File

@ -0,0 +1,27 @@
import { RuleObject } from 'antd/es/form';
import { z } from 'zod';
import { hostnameRegex, slugRegex } from '../../shared';
type Validator = (
rule: RuleObject,
value: any,
callback: (error?: string) => void
) => Promise<void | any> | void;
export const hostnameValidator: Validator = (rule, value, callback) => {
try {
z.union([z.string().ip(), z.string().regex(hostnameRegex)]).parse(value);
callback();
} catch (err) {
callback('Not valid host, it should be ip or hostname');
}
};
export const urlSlugValidator: Validator = (rule, value, callback) => {
try {
z.string().regex(slugRegex).parse(value);
callback();
} catch (err) {
callback('Not valid slug');
}
};

View File

@ -18,6 +18,13 @@ import { getSessionMetrics, getPageviewMetrics } from '../../model/website';
import { websiteInfoSchema } from '../../model/_schema'; import { websiteInfoSchema } from '../../model/_schema';
import { OpenApiMeta } from 'trpc-openapi'; import { OpenApiMeta } from 'trpc-openapi';
import { hostnameRegex } from '../../../shared'; import { hostnameRegex } from '../../../shared';
import { addWorkspaceWebsite } from '../../model/workspace';
const websiteNameSchema = z.string().max(100);
const websiteDomainSchema = z.union([
z.string().max(500).regex(hostnameRegex),
z.string().max(500).ip(),
]);
export const websiteRouter = router({ export const websiteRouter = router({
onlineCount: workspaceProcedure onlineCount: workspaceProcedure
@ -205,6 +212,29 @@ export const websiteRouter = router({
return []; return [];
}), }),
add: workspaceOwnerProcedure
.meta({
openapi: {
method: 'POST',
tags: [OPENAPI_TAG.WEBSITE],
protect: true,
path: `/workspace/{workspaceId}/website/add`,
},
})
.input(
z.object({
name: websiteNameSchema,
domain: websiteDomainSchema,
})
)
.output(websiteInfoSchema)
.mutation(async ({ input }) => {
const { workspaceId, name, domain } = input;
const website = await addWorkspaceWebsite(workspaceId, name, domain);
return website;
}),
updateInfo: workspaceOwnerProcedure updateInfo: workspaceOwnerProcedure
.meta( .meta(
buildWebsiteOpenapi({ buildWebsiteOpenapi({
@ -215,11 +245,8 @@ export const websiteRouter = router({
.input( .input(
z.object({ z.object({
websiteId: z.string().cuid2(), websiteId: z.string().cuid2(),
name: z.string().max(100), name: websiteNameSchema,
domain: z.union([ domain: websiteDomainSchema,
z.string().max(500).regex(hostnameRegex),
z.string().max(500).ip(),
]),
monitorId: z.string().cuid2().nullish(), monitorId: z.string().cuid2().nullish(),
}) })
) )