From 402b8a6955278937b8512307f304c428d4f3a441 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 24 Mar 2024 04:18:45 +0800 Subject: [PATCH] feat(v2): monitor feature --- src/client/api/model/monitor.ts | 4 +- src/client/components/CommonList.tsx | 12 ++-- src/client/index.css | 5 ++ src/client/pages/Layout/Nav.tsx | 45 ++++++++----- src/client/pages/LayoutV2.tsx | 10 +-- src/client/routeTree.gen.ts | 31 +++++++++ src/client/routes/index.tsx | 4 +- src/client/routes/monitor.tsx | 86 ++++++++++++++++++++++++ src/client/routes/monitor/$monitorId.tsx | 45 +++++++++++++ src/client/routes/monitor/add.tsx | 55 +++++++++++++++ src/client/routes/website/$websiteId.tsx | 4 +- src/client/routes/website/add.tsx | 4 +- 12 files changed, 267 insertions(+), 38 deletions(-) create mode 100644 src/client/routes/monitor.tsx create mode 100644 src/client/routes/monitor/$monitorId.tsx create mode 100644 src/client/routes/monitor/add.tsx diff --git a/src/client/api/model/monitor.ts b/src/client/api/model/monitor.ts index 7e9387b..bd1c380 100644 --- a/src/client/api/model/monitor.ts +++ b/src/client/api/model/monitor.ts @@ -1,10 +1,10 @@ import { defaultErrorHandler, defaultSuccessHandler, trpc } from '../trpc'; export function useMonitorUpsert() { - const context = trpc.useContext(); + const utils = trpc.useUtils(); const mutation = trpc.monitor.upsert.useMutation({ onSuccess: (data) => { - context.monitor.all.reset({ + utils.monitor.all.refetch({ workspaceId: data.workspaceId, }); diff --git a/src/client/components/CommonList.tsx b/src/client/components/CommonList.tsx index 6d9f0be..94d1ca1 100644 --- a/src/client/components/CommonList.tsx +++ b/src/client/components/CommonList.tsx @@ -10,7 +10,7 @@ import { useFuseSearch } from '@/hooks/useFuseSearch'; export interface CommonListItem { id: string; title: string; - content: React.ReactNode; + content?: React.ReactNode; tags: string[]; href: string; } @@ -46,12 +46,12 @@ export const CommonList: React.FC = React.memo((props) => { const finalList = searchResult ?? props.items; return ( -
+
{props.hasSearch && ( -
+
- + = React.memo((props) => {
-
+
{item.content}
{item.tags.length > 0 ? ( diff --git a/src/client/index.css b/src/client/index.css index 385ab98..00d6dfb 100644 --- a/src/client/index.css +++ b/src/client/index.css @@ -66,6 +66,11 @@ a { @tailwind components; @tailwind utilities; +/* use for adapt new design, avoid preflight conflict */ +.ant-btn-primary { + background-color: #1677ff; +} + /* https://ui.shadcn.com/themes */ @layer base { :root { diff --git a/src/client/pages/Layout/Nav.tsx b/src/client/pages/Layout/Nav.tsx index 3ea72b9..6c775c2 100644 --- a/src/client/pages/Layout/Nav.tsx +++ b/src/client/pages/Layout/Nav.tsx @@ -5,6 +5,7 @@ import { TooltipTrigger, } from '@/components/ui/tooltip'; import { cn } from '@/utils/style'; +import { Link, useRouterState } from '@tanstack/react-router'; import React from 'react'; import { IconType } from 'react-icons'; @@ -14,50 +15,59 @@ interface NavProps { title: string; label?: string; icon: IconType; - variant: 'default' | 'ghost'; + to: string; }[]; } export const Nav: React.FC = React.memo(({ links, isCollapsed }) => { + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + return (
); diff --git a/src/client/pages/LayoutV2.tsx b/src/client/pages/LayoutV2.tsx index 2c231fb..1cad7a4 100644 --- a/src/client/pages/LayoutV2.tsx +++ b/src/client/pages/LayoutV2.tsx @@ -85,31 +85,31 @@ export const LayoutV2: React.FC<{ title: 'Website', label: String(serviceCount?.website ?? ''), icon: LuAreaChart, - variant: 'default', + to: '/website', }, { title: 'Monitor', label: String(serviceCount?.monitor ?? ''), icon: LuMonitorDot, - variant: 'ghost', + to: '/monitor', }, { title: 'Servers', label: '', icon: LuServer, - variant: 'ghost', + to: '/server', }, { title: 'Telemetry', label: String(serviceCount?.telemetry ?? ''), icon: LuWifi, - variant: 'ghost', + to: '/telemetry', }, { title: 'Pages', label: String(serviceCount?.page ?? ''), icon: LuFilePieChart, - variant: 'ghost', + to: '/page', }, ]} /> diff --git a/src/client/routeTree.gen.ts b/src/client/routeTree.gen.ts index 6d61d70..b3697af 100644 --- a/src/client/routeTree.gen.ts +++ b/src/client/routeTree.gen.ts @@ -13,10 +13,13 @@ import { Route as rootRoute } from './routes/__root' import { Route as WebsiteImport } from './routes/website' import { Route as RegisterImport } from './routes/register' +import { Route as MonitorImport } from './routes/monitor' import { Route as LoginImport } from './routes/login' import { Route as IndexImport } from './routes/index' import { Route as WebsiteAddImport } from './routes/website/add' import { Route as WebsiteWebsiteIdImport } from './routes/website/$websiteId' +import { Route as MonitorAddImport } from './routes/monitor/add' +import { Route as MonitorMonitorIdImport } from './routes/monitor/$monitorId' // Create/Update Routes @@ -30,6 +33,11 @@ const RegisterRoute = RegisterImport.update({ getParentRoute: () => rootRoute, } as any) +const MonitorRoute = MonitorImport.update({ + path: '/monitor', + getParentRoute: () => rootRoute, +} as any) + const LoginRoute = LoginImport.update({ path: '/login', getParentRoute: () => rootRoute, @@ -50,6 +58,16 @@ const WebsiteWebsiteIdRoute = WebsiteWebsiteIdImport.update({ getParentRoute: () => WebsiteRoute, } as any) +const MonitorAddRoute = MonitorAddImport.update({ + path: '/add', + getParentRoute: () => MonitorRoute, +} as any) + +const MonitorMonitorIdRoute = MonitorMonitorIdImport.update({ + path: '/$monitorId', + getParentRoute: () => MonitorRoute, +} as any) + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -62,6 +80,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } + '/monitor': { + preLoaderRoute: typeof MonitorImport + parentRoute: typeof rootRoute + } '/register': { preLoaderRoute: typeof RegisterImport parentRoute: typeof rootRoute @@ -70,6 +92,14 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof WebsiteImport parentRoute: typeof rootRoute } + '/monitor/$monitorId': { + preLoaderRoute: typeof MonitorMonitorIdImport + parentRoute: typeof MonitorImport + } + '/monitor/add': { + preLoaderRoute: typeof MonitorAddImport + parentRoute: typeof MonitorImport + } '/website/$websiteId': { preLoaderRoute: typeof WebsiteWebsiteIdImport parentRoute: typeof WebsiteImport @@ -86,6 +116,7 @@ declare module '@tanstack/react-router' { export const routeTree = rootRoute.addChildren([ IndexRoute, LoginRoute, + MonitorRoute.addChildren([MonitorMonitorIdRoute, MonitorAddRoute]), RegisterRoute, WebsiteRoute.addChildren([WebsiteWebsiteIdRoute, WebsiteAddRoute]), ]) diff --git a/src/client/routes/index.tsx b/src/client/routes/index.tsx index 3034c34..95a98f0 100644 --- a/src/client/routes/index.tsx +++ b/src/client/routes/index.tsx @@ -1,9 +1,9 @@ import { createFileRoute, redirect } from '@tanstack/react-router'; export const Route = createFileRoute('/')({ - beforeLoad: () => { + beforeLoad: ({ context }) => { redirect({ - to: '/website', + to: context.userInfo ? '/website' : '/login', }); }, }); diff --git a/src/client/routes/monitor.tsx b/src/client/routes/monitor.tsx new file mode 100644 index 0000000..d4a7f49 --- /dev/null +++ b/src/client/routes/monitor.tsx @@ -0,0 +1,86 @@ +import { trpc } from '@/api/trpc'; +import { CommonHeader } from '@/components/CommonHeader'; +import { CommonList } from '@/components/CommonList'; +import { CommonWrapper } from '@/components/CommonWrapper'; +import { Button } from '@/components/ui/button'; +import { useDataReady } from '@/hooks/useDataReady'; +import { useEvent } from '@/hooks/useEvent'; +import { LayoutV2 } from '@/pages/LayoutV2'; +import { useCurrentWorkspaceId } from '@/store/user'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { useTranslation } from '@i18next-toolkit/react'; +import { + createFileRoute, + useNavigate, + useRouterState, +} from '@tanstack/react-router'; +import { LuPlus } from 'react-icons/lu'; + +export const Route = createFileRoute('/monitor')({ + beforeLoad: routeAuthBeforeLoad, + component: MonitorComponent, +}); + +function MonitorComponent() { + const workspaceId = useCurrentWorkspaceId(); + const { t } = useTranslation(); + const { data = [] } = trpc.monitor.all.useQuery({ + workspaceId, + }); + const navigate = useNavigate(); + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + + const items = data.map((item) => ({ + id: item.id, + title: item.name, + tags: [item.type], + href: `/monitor/${item.id}`, + })); + + useDataReady( + () => data.length > 0, + () => { + if (pathname === Route.fullPath) { + navigate({ + to: '/monitor/$monitorId', + params: { + monitorId: data[0].id, + }, + }); + } + } + ); + + const handleClickAdd = useEvent(() => { + navigate({ + to: '/monitor/add', + }); + }); + + return ( + + {t('Add')} + + } + /> + } + > + + + } + /> + ); +} diff --git a/src/client/routes/monitor/$monitorId.tsx b/src/client/routes/monitor/$monitorId.tsx new file mode 100644 index 0000000..9510aef --- /dev/null +++ b/src/client/routes/monitor/$monitorId.tsx @@ -0,0 +1,45 @@ +import { trpc } from '@/api/trpc'; +import { CommonHeader } from '@/components/CommonHeader'; +import { CommonWrapper } from '@/components/CommonWrapper'; +import { ErrorTip } from '@/components/ErrorTip'; +import { Loading } from '@/components/Loading'; +import { NotFoundTip } from '@/components/NotFoundTip'; +import { MonitorInfo } from '@/components/monitor/MonitorInfo'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { useCurrentWorkspaceId } from '@/store/user'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/monitor/$monitorId')({ + beforeLoad: routeAuthBeforeLoad, + component: MonitorDetailComponent, +}); + +function MonitorDetailComponent() { + const { monitorId } = Route.useParams<{ monitorId: string }>(); + const workspaceId = useCurrentWorkspaceId(); + const { data: monitor, isLoading } = trpc.monitor.get.useQuery({ + workspaceId, + monitorId, + }); + + if (!monitorId) { + return ; + } + + if (isLoading) { + return ; + } + + if (!monitor) { + return ; + } + + return ( + }> + + + + + ); +} diff --git a/src/client/routes/monitor/add.tsx b/src/client/routes/monitor/add.tsx new file mode 100644 index 0000000..c52531b --- /dev/null +++ b/src/client/routes/monitor/add.tsx @@ -0,0 +1,55 @@ +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { t, useTranslation } from '@i18next-toolkit/react'; +import { Button } from '@/components/ui/button'; +import { useEvent } from '@/hooks/useEvent'; +import { useCurrentWorkspaceId } from '@/store/user'; +import { trpc } from '@/api/trpc'; +import { Card, CardContent, CardFooter } from '@/components/ui/card'; +import { CommonWrapper } from '@/components/CommonWrapper'; +import { + MonitorInfoEditor, + MonitorInfoEditorValues, +} from '@/components/monitor/MonitorInfoEditor'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { useMonitorUpsert } from '@/api/model/monitor'; + +export const Route = createFileRoute('/monitor/add')({ + beforeLoad: routeAuthBeforeLoad, + component: MonitorAddComponent, +}); + +function MonitorAddComponent() { + const { t } = useTranslation(); + const workspaceId = useCurrentWorkspaceId(); + const addWebsiteMutation = trpc.website.add.useMutation(); + const navigate = useNavigate(); + const mutation = useMonitorUpsert(); + + const handleSubmit = useEvent(async (values: MonitorInfoEditorValues) => { + const res = await mutation.mutateAsync({ + ...values, + workspaceId, + }); + + navigate({ + to: '/monitor/$monitorId', + params: { + monitorId: res.id, + }, + }); + }); + + return ( + {t('Add Monitor')}} + > +
+ + + + + +
+
+ ); +} diff --git a/src/client/routes/website/$websiteId.tsx b/src/client/routes/website/$websiteId.tsx index 7c7c7fe..71761ef 100644 --- a/src/client/routes/website/$websiteId.tsx +++ b/src/client/routes/website/$websiteId.tsx @@ -4,7 +4,6 @@ import { CommonWrapper } from '@/components/CommonWrapper'; import { ErrorTip } from '@/components/ErrorTip'; import { Loading } from '@/components/Loading'; import { NotFoundTip } from '@/components/NotFoundTip'; -import { Button } from '@/components/ui/button'; import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; import { WebsiteCodeBtn } from '@/components/website/WebsiteCodeBtn'; import { WebsiteMetricsTable } from '@/components/website/WebsiteMetricsTable'; @@ -14,9 +13,8 @@ import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate'; import { useCurrentWorkspaceId } from '@/store/user'; import { routeAuthBeforeLoad } from '@/utils/route'; import { useTranslation } from '@i18next-toolkit/react'; -import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { createFileRoute } from '@tanstack/react-router'; import { Card } from 'antd'; -import { LuArrowRight } from 'react-icons/lu'; export const Route = createFileRoute('/website/$websiteId')({ beforeLoad: routeAuthBeforeLoad, diff --git a/src/client/routes/website/add.tsx b/src/client/routes/website/add.tsx index 7a54b2b..365172a 100644 --- a/src/client/routes/website/add.tsx +++ b/src/client/routes/website/add.tsx @@ -25,7 +25,7 @@ import { CommonWrapper } from '@/components/CommonWrapper'; export const Route = createFileRoute('/website/add')({ beforeLoad: routeAuthBeforeLoad, - component: WebsiteDetailComponent, + component: WebsiteAddComponent, }); const addFormSchema = z.object({ @@ -33,7 +33,7 @@ const addFormSchema = z.object({ domain: z.union([z.string().ip(), z.string().regex(hostnameRegex)]), }); -function WebsiteDetailComponent() { +function WebsiteAddComponent() { const { t } = useTranslation(); const workspaceId = useCurrentWorkspaceId(); const addWebsiteMutation = trpc.website.add.useMutation();