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:
parent
824bd89ede
commit
a04aa67f85
@ -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');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -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');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -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>
|
||||||
|
@ -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) => {
|
||||||
|
27
src/client/utils/validator.ts
Normal file
27
src/client/utils/validator.ts
Normal 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');
|
||||||
|
}
|
||||||
|
};
|
@ -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(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user