feat: add status list and add route
This commit is contained in:
parent
669b2ed8b8
commit
0cf934c9e3
94
src/client/pages/Monitor/PageAdd.tsx
Normal file
94
src/client/pages/Monitor/PageAdd.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
|
import { trpc } from '../../api/trpc';
|
||||||
|
import { Button, Form, Input, Typography } from 'antd';
|
||||||
|
import { useEvent } from '../../hooks/useEvent';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { slugRegex } from '../../../shared';
|
||||||
|
|
||||||
|
const { Text, Paragraph } = Typography;
|
||||||
|
|
||||||
|
interface Values {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MonitorPageAdd: React.FC = React.memo(() => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId()!;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const createPageMutation = trpc.monitor.createPage.useMutation();
|
||||||
|
const trpcUtils = trpc.useContext();
|
||||||
|
|
||||||
|
const handleFinish = useEvent(async (values: Values) => {
|
||||||
|
await createPageMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
title: values.title,
|
||||||
|
slug: values.slug,
|
||||||
|
});
|
||||||
|
|
||||||
|
trpcUtils.monitor.getAllPages.refetch();
|
||||||
|
|
||||||
|
navigate('/monitor/pages');
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-8 py-4">
|
||||||
|
<Form<Values> layout="vertical" onFinish={handleFinish}>
|
||||||
|
<Form.Item
|
||||||
|
label="Title"
|
||||||
|
name="title"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Slug"
|
||||||
|
name="slug"
|
||||||
|
extra={
|
||||||
|
<div className="pt-2">
|
||||||
|
<div>
|
||||||
|
Accept characters: <Text code>a-z</Text> <Text code>0-9</Text>{' '}
|
||||||
|
<Text code>-</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
No consecutive dashes <Text code>--</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator(rule, value, callback) {
|
||||||
|
try {
|
||||||
|
z.string().regex(slugRegex).parse(value);
|
||||||
|
callback();
|
||||||
|
} catch (err) {
|
||||||
|
callback('Not valid slug');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input addonBefore={`${window.origin}/status/`} />
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={createPageMutation.isLoading}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MonitorPageAdd.displayName = 'MonitorPageAdd';
|
40
src/client/pages/Monitor/PageList.tsx
Normal file
40
src/client/pages/Monitor/PageList.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
|
import { trpc } from '../../api/trpc';
|
||||||
|
import { Button, Card } from 'antd';
|
||||||
|
import { EditOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
export const MonitorPageList: React.FC = React.memo(() => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId()!;
|
||||||
|
const { data: pages = [] } = trpc.monitor.getAllPages.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="px-8 py-4">
|
||||||
|
<Button type="primary" onClick={() => navigate('/monitor/pages/add')}>
|
||||||
|
New page
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="mt-4 flex flex-col gap-2">
|
||||||
|
{pages.map((p) => (
|
||||||
|
<Card bodyStyle={{ padding: 12 }}>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="flex-1">{p.title}</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => navigate(`/status/${p.slug}`)}
|
||||||
|
/>
|
||||||
|
<Button icon={<EditOutlined />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MonitorPageList.displayName = 'MonitorPageList';
|
@ -5,6 +5,9 @@ import { MonitorAdd } from './Add';
|
|||||||
import { MonitorDetail } from './Detail';
|
import { MonitorDetail } from './Detail';
|
||||||
import { MonitorEdit } from './Edit';
|
import { MonitorEdit } from './Edit';
|
||||||
import { MonitorOverview } from './Overview';
|
import { MonitorOverview } from './Overview';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import { MonitorPageList } from './PageList';
|
||||||
|
import { MonitorPageAdd } from './PageAdd';
|
||||||
|
|
||||||
export const MonitorPage: React.FC = React.memo(() => {
|
export const MonitorPage: React.FC = React.memo(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -12,13 +15,21 @@ export const MonitorPage: React.FC = React.memo(() => {
|
|||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
<div className="px-4 pt-4">
|
<div className="px-4 pt-4 flex gap-4">
|
||||||
<div
|
<Button
|
||||||
className="px-3 py-2 rounded-full bg-green-400 hover:bg-green-500 text-white dark:text-gray-700 inline-block cursor-pointer"
|
type="primary"
|
||||||
|
size="large"
|
||||||
onClick={() => navigate('/monitor/add')}
|
onClick={() => navigate('/monitor/add')}
|
||||||
>
|
>
|
||||||
Add new Montior
|
Add new Montior
|
||||||
</div>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="large"
|
||||||
|
onClick={() => navigate('/monitor/pages')}
|
||||||
|
>
|
||||||
|
Pages
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-5 flex flex-1 overflow-hidden">
|
<div className="py-5 flex flex-1 overflow-hidden">
|
||||||
@ -31,6 +42,8 @@ export const MonitorPage: React.FC = React.memo(() => {
|
|||||||
<Route path="/:monitorId" element={<MonitorDetail />} />
|
<Route path="/:monitorId" element={<MonitorDetail />} />
|
||||||
<Route path="/:monitorId/edit" element={<MonitorEdit />} />
|
<Route path="/:monitorId/edit" element={<MonitorEdit />} />
|
||||||
<Route path="/add" element={<MonitorAdd />} />
|
<Route path="/add" element={<MonitorAdd />} />
|
||||||
|
<Route path="/pages" element={<MonitorPageList />} />
|
||||||
|
<Route path="/pages/add" element={<MonitorPageAdd />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -418,6 +418,23 @@ export const monitorRouter = router({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
getAllPages: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildMonitorOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/getAllPages',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.array(MonitorStatusPageModelSchema))
|
||||||
|
.query(({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
return prisma.monitorStatusPage.findMany({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
createPage: workspaceOwnerProcedure
|
createPage: workspaceOwnerProcedure
|
||||||
.meta(
|
.meta(
|
||||||
buildMonitorOpenapi({
|
buildMonitorOpenapi({
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
export const hostnameRegex =
|
export const hostnameRegex =
|
||||||
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||||
|
|
||||||
|
export const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
||||||
|
Loading…
Reference in New Issue
Block a user