diff --git a/src/client/pages/Monitor/PageAdd.tsx b/src/client/pages/Monitor/PageAdd.tsx
new file mode 100644
index 0000000..cdc0ab9
--- /dev/null
+++ b/src/client/pages/Monitor/PageAdd.tsx
@@ -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 (
+
+
+
+
+
+
+
+ Accept characters: a-z 0-9{' '}
+ -
+
+
+ No consecutive dashes --
+
+
+ }
+ rules={[
+ {
+ required: true,
+ },
+ {
+ validator(rule, value, callback) {
+ try {
+ z.string().regex(slugRegex).parse(value);
+ callback();
+ } catch (err) {
+ callback('Not valid slug');
+ }
+ },
+ },
+ ]}
+ >
+
+
+
+
+
+ );
+});
+MonitorPageAdd.displayName = 'MonitorPageAdd';
diff --git a/src/client/pages/Monitor/PageList.tsx b/src/client/pages/Monitor/PageList.tsx
new file mode 100644
index 0000000..15f61a9
--- /dev/null
+++ b/src/client/pages/Monitor/PageList.tsx
@@ -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 (
+
+
+
+
+ {pages.map((p) => (
+
+
+
{p.title}
+
+ }
+ onClick={() => navigate(`/status/${p.slug}`)}
+ />
+ } />
+
+
+
+ ))}
+
+
+ );
+});
+MonitorPageList.displayName = 'MonitorPageList';
diff --git a/src/client/pages/Monitor/index.tsx b/src/client/pages/Monitor/index.tsx
index 23e302c..8914bf2 100644
--- a/src/client/pages/Monitor/index.tsx
+++ b/src/client/pages/Monitor/index.tsx
@@ -5,6 +5,9 @@ import { MonitorAdd } from './Add';
import { MonitorDetail } from './Detail';
import { MonitorEdit } from './Edit';
import { MonitorOverview } from './Overview';
+import { Button } from 'antd';
+import { MonitorPageList } from './PageList';
+import { MonitorPageAdd } from './PageAdd';
export const MonitorPage: React.FC = React.memo(() => {
const navigate = useNavigate();
@@ -12,13 +15,21 @@ export const MonitorPage: React.FC = React.memo(() => {
return (
-
-
+
+
+
@@ -31,6 +42,8 @@ export const MonitorPage: React.FC = React.memo(() => {
} />
} />
} />
+ } />
+ } />
diff --git a/src/server/trpc/routers/monitor.ts b/src/server/trpc/routers/monitor.ts
index d85b444..3d354b1 100644
--- a/src/server/trpc/routers/monitor.ts
+++ b/src/server/trpc/routers/monitor.ts
@@ -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
.meta(
buildMonitorOpenapi({
diff --git a/src/shared/regex.ts b/src/shared/regex.ts
index b37ddd3..044ffa9 100644
--- a/src/shared/regex.ts
+++ b/src/shared/regex.ts
@@ -1,2 +1,4 @@
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])$/;
+
+export const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;