From a73720411cd16376e01dac4fc27fb8d29b057045 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Thu, 18 Apr 2024 00:20:01 +0800 Subject: [PATCH] feat: add custom domain support for status page --- .../monitor/StatusPage/EditForm.tsx | 22 ++++- .../components/monitor/StatusPage/index.tsx | 15 +++- src/client/utils/validator.ts | 9 ++ src/server/app.ts | 22 ++++- src/server/model/monitor/page/manager.ts | 84 +++++++++++++++++++ .../migration.sql | 2 + src/server/prisma/schema.prisma | 1 + src/server/prisma/zod/monitorstatuspage.ts | 1 + src/server/trpc/routers/monitor.ts | 43 ++++++++-- src/server/ws/index.ts | 3 +- 10 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 src/server/model/monitor/page/manager.ts create mode 100644 src/server/prisma/migrations/20240417150948_add_status_page_domain/migration.sql diff --git a/src/client/components/monitor/StatusPage/EditForm.tsx b/src/client/components/monitor/StatusPage/EditForm.tsx index eff9612..0665d12 100644 --- a/src/client/components/monitor/StatusPage/EditForm.tsx +++ b/src/client/components/monitor/StatusPage/EditForm.tsx @@ -1,7 +1,7 @@ import { Switch, Divider, Form, Input, Typography } from 'antd'; import React from 'react'; import { MonitorPicker } from '../MonitorPicker'; -import { urlSlugValidator } from '../../../utils/validator'; +import { domainValidator, urlSlugValidator } from '../../../utils/validator'; import { useTranslation } from '@i18next-toolkit/react'; import { Button } from '@/components/ui/button'; import { LuMinusCircle, LuPlus } from 'react-icons/lu'; @@ -12,6 +12,7 @@ export interface MonitorStatusPageEditFormValues { title: string; slug: string; monitorList: PrismaJson.MonitorStatusPageList; + domain: string; } interface MonitorStatusPageEditFormProps { @@ -71,6 +72,25 @@ export const MonitorStatusPageEditForm: React.FC + + {t( + 'You can config your status page in your own domain, for example: status.example.com' + )} + + } + rules={[ + { + validator: domainValidator, + }, + ]} + > + + + {(fields, { add, remove }, { errors }) => { return ( diff --git a/src/client/components/monitor/StatusPage/index.tsx b/src/client/components/monitor/StatusPage/index.tsx index 868cc6d..df2f4f7 100644 --- a/src/client/components/monitor/StatusPage/index.tsx +++ b/src/client/components/monitor/StatusPage/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { trpc } from '../../../api/trpc'; import { useAllowEdit } from './useAllowEdit'; import { @@ -38,6 +38,17 @@ export const MonitorStatusPage: React.FC = React.memo( const monitorList = info?.monitorList ?? []; + const initialValues = useMemo(() => { + if (!info) { + return {}; + } + + return { + ...info, + domain: info.domain ?? '', + }; + }, [info]); + const [{ loading }, handleSave] = useRequest( async (values: MonitorStatusPageEditFormValues) => { if (!info) { @@ -84,7 +95,7 @@ export const MonitorStatusPage: React.FC = React.memo(
setEditMode(false)} /> diff --git a/src/client/utils/validator.ts b/src/client/utils/validator.ts index e676f9c..3348f80 100644 --- a/src/client/utils/validator.ts +++ b/src/client/utils/validator.ts @@ -17,6 +17,15 @@ export const hostnameValidator: Validator = (rule, value, callback) => { } }; +export const domainValidator: Validator = (rule, value, callback) => { + try { + z.string().regex(hostnameRegex).parse(value); + callback(); + } catch (err) { + callback('Not valid, it should be domain, for example: example.com'); + } +}; + export const urlSlugValidator: Validator = (rule, value, callback) => { try { z.string().regex(slugRegex).parse(value); diff --git a/src/server/app.ts b/src/server/app.ts index b0434c9..2a89761 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -19,6 +19,7 @@ import { logger } from './utils/logger'; import { monitorRouter } from './router/monitor'; import { healthRouter } from './router/health'; import path from 'path'; +import { monitorPageManager } from './model/monitor/page/manager'; const app = express(); @@ -27,7 +28,6 @@ app.use(express.json()); app.use(passport.initialize()); app.use(morgan('tiny')); app.use(cors()); -app.use(express.static('public')); // http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header app.disable('x-powered-by'); @@ -60,6 +60,26 @@ if (env.allowOpenapi) { app.use('/open', trpcOpenapiHttpHandler); } +// Custom Status Page +app.use('/*', (req, res, next) => { + if (req.baseUrl === '/' || req.baseUrl === '') { + const customDomain = monitorPageManager.findPageDomain(req.hostname); + if (customDomain) { + res + .status(200) + .send( + `