feat(v2): monitor feature
This commit is contained in:
parent
958b1c0932
commit
402b8a6955
@ -1,10 +1,10 @@
|
|||||||
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '../trpc';
|
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '../trpc';
|
||||||
|
|
||||||
export function useMonitorUpsert() {
|
export function useMonitorUpsert() {
|
||||||
const context = trpc.useContext();
|
const utils = trpc.useUtils();
|
||||||
const mutation = trpc.monitor.upsert.useMutation({
|
const mutation = trpc.monitor.upsert.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
context.monitor.all.reset({
|
utils.monitor.all.refetch({
|
||||||
workspaceId: data.workspaceId,
|
workspaceId: data.workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { useFuseSearch } from '@/hooks/useFuseSearch';
|
|||||||
export interface CommonListItem {
|
export interface CommonListItem {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
content: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
href: string;
|
href: string;
|
||||||
}
|
}
|
||||||
@ -46,12 +46,12 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
|
|||||||
const finalList = searchResult ?? props.items;
|
const finalList = searchResult ?? props.items;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex h-full flex-col">
|
||||||
{props.hasSearch && (
|
{props.hasSearch && (
|
||||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<div className="bg-background/95 supports-[backdrop-filter]:bg-background/60 p-4 backdrop-blur">
|
||||||
<form>
|
<form>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<LuSearch className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
<LuSearch className="text-muted-foreground absolute left-2 top-2.5 h-4 w-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="pl-8"
|
className="pl-8"
|
||||||
@ -72,7 +72,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
|
|||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent',
|
'hover:bg-accent flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all',
|
||||||
isSelected && 'bg-muted'
|
isSelected && 'bg-muted'
|
||||||
)}
|
)}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -88,7 +88,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="line-clamp-2 text-xs text-muted-foreground">
|
<div className="text-muted-foreground line-clamp-2 text-xs">
|
||||||
{item.content}
|
{item.content}
|
||||||
</div>
|
</div>
|
||||||
{item.tags.length > 0 ? (
|
{item.tags.length > 0 ? (
|
||||||
|
@ -66,6 +66,11 @@ a {
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* use for adapt new design, avoid preflight conflict */
|
||||||
|
.ant-btn-primary {
|
||||||
|
background-color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
/* https://ui.shadcn.com/themes */
|
/* https://ui.shadcn.com/themes */
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
|
import { Link, useRouterState } from '@tanstack/react-router';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconType } from 'react-icons';
|
import { IconType } from 'react-icons';
|
||||||
|
|
||||||
@ -14,50 +15,59 @@ interface NavProps {
|
|||||||
title: string;
|
title: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
icon: IconType;
|
icon: IconType;
|
||||||
variant: 'default' | 'ghost';
|
to: string;
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Nav: React.FC<NavProps> = React.memo(({ links, isCollapsed }) => {
|
export const Nav: React.FC<NavProps> = React.memo(({ links, isCollapsed }) => {
|
||||||
|
const pathname = useRouterState({
|
||||||
|
select: (state) => state.location.pathname,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-collapsed={isCollapsed}
|
data-collapsed={isCollapsed}
|
||||||
className="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
|
className="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
|
||||||
>
|
>
|
||||||
<nav className="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
|
<nav className="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2">
|
||||||
{links.map((link, index) =>
|
{links.map((link, index) => {
|
||||||
isCollapsed ? (
|
const isSelect = pathname.startsWith(link.to);
|
||||||
|
const variant = isSelect ? 'default' : 'ghost';
|
||||||
|
|
||||||
|
return isCollapsed ? (
|
||||||
<Tooltip key={index} delayDuration={0}>
|
<Tooltip key={index} delayDuration={0}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div
|
<Link
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant: link.variant, size: 'icon' }),
|
buttonVariants({ variant: variant, size: 'icon' }),
|
||||||
'h-9 w-9 cursor-pointer',
|
'h-9 w-9 cursor-pointer',
|
||||||
link.variant === 'default' &&
|
variant === 'default' &&
|
||||||
'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
||||||
)}
|
)}
|
||||||
|
to={link.to}
|
||||||
>
|
>
|
||||||
<link.icon className="h-4 w-4" />
|
<link.icon className="h-4 w-4" />
|
||||||
<span className="sr-only">{link.title}</span>
|
<span className="sr-only">{link.title}</span>
|
||||||
</div>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right" className="flex items-center gap-4">
|
<TooltipContent side="right" className="flex items-center gap-4">
|
||||||
{link.title}
|
{link.title}
|
||||||
{link.label && (
|
{link.label && (
|
||||||
<span className="ml-auto text-muted-foreground">
|
<span className="text-muted-foreground ml-auto">
|
||||||
{link.label}
|
{link.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<Link
|
||||||
key={index}
|
key={index}
|
||||||
|
to={link.to}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant: link.variant, size: 'sm' }),
|
buttonVariants({ variant: variant, size: 'sm' }),
|
||||||
link.variant === 'default' &&
|
variant === 'default' &&
|
||||||
'dark:bg-muted dark:text-white dark:hover:bg-muted dark:hover:text-white',
|
'dark:bg-muted dark:hover:bg-muted dark:text-white dark:hover:text-white',
|
||||||
'justify-start cursor-pointer'
|
'cursor-pointer justify-start'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<link.icon className="mr-2 h-4 w-4" />
|
<link.icon className="mr-2 h-4 w-4" />
|
||||||
@ -66,16 +76,15 @@ export const Nav: React.FC<NavProps> = React.memo(({ links, isCollapsed }) => {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'ml-auto',
|
'ml-auto',
|
||||||
link.variant === 'default' &&
|
variant === 'default' && 'text-background dark:text-white'
|
||||||
'text-background dark:text-white'
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{link.label}
|
{link.label}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Link>
|
||||||
)
|
);
|
||||||
)}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -85,31 +85,31 @@ export const LayoutV2: React.FC<{
|
|||||||
title: 'Website',
|
title: 'Website',
|
||||||
label: String(serviceCount?.website ?? ''),
|
label: String(serviceCount?.website ?? ''),
|
||||||
icon: LuAreaChart,
|
icon: LuAreaChart,
|
||||||
variant: 'default',
|
to: '/website',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Monitor',
|
title: 'Monitor',
|
||||||
label: String(serviceCount?.monitor ?? ''),
|
label: String(serviceCount?.monitor ?? ''),
|
||||||
icon: LuMonitorDot,
|
icon: LuMonitorDot,
|
||||||
variant: 'ghost',
|
to: '/monitor',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Servers',
|
title: 'Servers',
|
||||||
label: '',
|
label: '',
|
||||||
icon: LuServer,
|
icon: LuServer,
|
||||||
variant: 'ghost',
|
to: '/server',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Telemetry',
|
title: 'Telemetry',
|
||||||
label: String(serviceCount?.telemetry ?? ''),
|
label: String(serviceCount?.telemetry ?? ''),
|
||||||
icon: LuWifi,
|
icon: LuWifi,
|
||||||
variant: 'ghost',
|
to: '/telemetry',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pages',
|
title: 'Pages',
|
||||||
label: String(serviceCount?.page ?? ''),
|
label: String(serviceCount?.page ?? ''),
|
||||||
icon: LuFilePieChart,
|
icon: LuFilePieChart,
|
||||||
variant: 'ghost',
|
to: '/page',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
import { Route as WebsiteImport } from './routes/website'
|
import { Route as WebsiteImport } from './routes/website'
|
||||||
import { Route as RegisterImport } from './routes/register'
|
import { Route as RegisterImport } from './routes/register'
|
||||||
|
import { Route as MonitorImport } from './routes/monitor'
|
||||||
import { Route as LoginImport } from './routes/login'
|
import { Route as LoginImport } from './routes/login'
|
||||||
import { Route as IndexImport } from './routes/index'
|
import { Route as IndexImport } from './routes/index'
|
||||||
import { Route as WebsiteAddImport } from './routes/website/add'
|
import { Route as WebsiteAddImport } from './routes/website/add'
|
||||||
import { Route as WebsiteWebsiteIdImport } from './routes/website/$websiteId'
|
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
|
// Create/Update Routes
|
||||||
|
|
||||||
@ -30,6 +33,11 @@ const RegisterRoute = RegisterImport.update({
|
|||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const MonitorRoute = MonitorImport.update({
|
||||||
|
path: '/monitor',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const LoginRoute = LoginImport.update({
|
const LoginRoute = LoginImport.update({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
getParentRoute: () => rootRoute,
|
getParentRoute: () => rootRoute,
|
||||||
@ -50,6 +58,16 @@ const WebsiteWebsiteIdRoute = WebsiteWebsiteIdImport.update({
|
|||||||
getParentRoute: () => WebsiteRoute,
|
getParentRoute: () => WebsiteRoute,
|
||||||
} as any)
|
} 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
|
// Populate the FileRoutesByPath interface
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
@ -62,6 +80,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof LoginImport
|
preLoaderRoute: typeof LoginImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/monitor': {
|
||||||
|
preLoaderRoute: typeof MonitorImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/register': {
|
'/register': {
|
||||||
preLoaderRoute: typeof RegisterImport
|
preLoaderRoute: typeof RegisterImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
@ -70,6 +92,14 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof WebsiteImport
|
preLoaderRoute: typeof WebsiteImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/monitor/$monitorId': {
|
||||||
|
preLoaderRoute: typeof MonitorMonitorIdImport
|
||||||
|
parentRoute: typeof MonitorImport
|
||||||
|
}
|
||||||
|
'/monitor/add': {
|
||||||
|
preLoaderRoute: typeof MonitorAddImport
|
||||||
|
parentRoute: typeof MonitorImport
|
||||||
|
}
|
||||||
'/website/$websiteId': {
|
'/website/$websiteId': {
|
||||||
preLoaderRoute: typeof WebsiteWebsiteIdImport
|
preLoaderRoute: typeof WebsiteWebsiteIdImport
|
||||||
parentRoute: typeof WebsiteImport
|
parentRoute: typeof WebsiteImport
|
||||||
@ -86,6 +116,7 @@ declare module '@tanstack/react-router' {
|
|||||||
export const routeTree = rootRoute.addChildren([
|
export const routeTree = rootRoute.addChildren([
|
||||||
IndexRoute,
|
IndexRoute,
|
||||||
LoginRoute,
|
LoginRoute,
|
||||||
|
MonitorRoute.addChildren([MonitorMonitorIdRoute, MonitorAddRoute]),
|
||||||
RegisterRoute,
|
RegisterRoute,
|
||||||
WebsiteRoute.addChildren([WebsiteWebsiteIdRoute, WebsiteAddRoute]),
|
WebsiteRoute.addChildren([WebsiteWebsiteIdRoute, WebsiteAddRoute]),
|
||||||
])
|
])
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute, redirect } from '@tanstack/react-router';
|
import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
export const Route = createFileRoute('/')({
|
||||||
beforeLoad: () => {
|
beforeLoad: ({ context }) => {
|
||||||
redirect({
|
redirect({
|
||||||
to: '/website',
|
to: context.userInfo ? '/website' : '/login',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
86
src/client/routes/monitor.tsx
Normal file
86
src/client/routes/monitor.tsx
Normal file
@ -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 (
|
||||||
|
<LayoutV2
|
||||||
|
list={
|
||||||
|
<CommonWrapper
|
||||||
|
header={
|
||||||
|
<CommonHeader
|
||||||
|
title={t('Monitor')}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
Icon={LuPlus}
|
||||||
|
onClick={handleClickAdd}
|
||||||
|
>
|
||||||
|
{t('Add')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CommonList hasSearch={true} items={items} />
|
||||||
|
</CommonWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
45
src/client/routes/monitor/$monitorId.tsx
Normal file
45
src/client/routes/monitor/$monitorId.tsx
Normal file
@ -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 <ErrorTip />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitor) {
|
||||||
|
return <NotFoundTip />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper header={<CommonHeader title={monitor.name} />}>
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
<MonitorInfo monitorId={monitor.id} />
|
||||||
|
</ScrollArea>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
55
src/client/routes/monitor/add.tsx
Normal file
55
src/client/routes/monitor/add.tsx
Normal file
@ -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 (
|
||||||
|
<CommonWrapper
|
||||||
|
header={<h1 className="text-xl font-bold">{t('Add Monitor')}</h1>}
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-4">
|
||||||
|
<MonitorInfoEditor onSave={handleSubmit} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -4,7 +4,6 @@ import { CommonWrapper } from '@/components/CommonWrapper';
|
|||||||
import { ErrorTip } from '@/components/ErrorTip';
|
import { ErrorTip } from '@/components/ErrorTip';
|
||||||
import { Loading } from '@/components/Loading';
|
import { Loading } from '@/components/Loading';
|
||||||
import { NotFoundTip } from '@/components/NotFoundTip';
|
import { NotFoundTip } from '@/components/NotFoundTip';
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area';
|
||||||
import { WebsiteCodeBtn } from '@/components/website/WebsiteCodeBtn';
|
import { WebsiteCodeBtn } from '@/components/website/WebsiteCodeBtn';
|
||||||
import { WebsiteMetricsTable } from '@/components/website/WebsiteMetricsTable';
|
import { WebsiteMetricsTable } from '@/components/website/WebsiteMetricsTable';
|
||||||
@ -14,9 +13,8 @@ import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate';
|
|||||||
import { useCurrentWorkspaceId } from '@/store/user';
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { Card } from 'antd';
|
import { Card } from 'antd';
|
||||||
import { LuArrowRight } from 'react-icons/lu';
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/website/$websiteId')({
|
export const Route = createFileRoute('/website/$websiteId')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
@ -25,7 +25,7 @@ import { CommonWrapper } from '@/components/CommonWrapper';
|
|||||||
|
|
||||||
export const Route = createFileRoute('/website/add')({
|
export const Route = createFileRoute('/website/add')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
component: WebsiteDetailComponent,
|
component: WebsiteAddComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
const addFormSchema = z.object({
|
const addFormSchema = z.object({
|
||||||
@ -33,7 +33,7 @@ const addFormSchema = z.object({
|
|||||||
domain: z.union([z.string().ip(), z.string().regex(hostnameRegex)]),
|
domain: z.union([z.string().ip(), z.string().regex(hostnameRegex)]),
|
||||||
});
|
});
|
||||||
|
|
||||||
function WebsiteDetailComponent() {
|
function WebsiteAddComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const addWebsiteMutation = trpc.website.add.useMutation();
|
const addWebsiteMutation = trpc.website.add.useMutation();
|
||||||
|
Loading…
Reference in New Issue
Block a user