feat: add monitor bind website

This commit is contained in:
moonrailgun 2023-10-15 00:51:03 +08:00
parent df7d18c2a0
commit 194103eb2d
10 changed files with 89 additions and 22 deletions

View File

@ -63,6 +63,7 @@
"request-ip": "^3.3.0", "request-ip": "^3.3.0",
"socket.io": "^4.7.2", "socket.io": "^4.7.2",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",
"str2int": "^1.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"vite-express": "^0.10.0", "vite-express": "^0.10.0",

View File

@ -142,6 +142,9 @@ dependencies:
socket.io-client: socket.io-client:
specifier: ^4.7.2 specifier: ^4.7.2
version: 4.7.2 version: 4.7.2
str2int:
specifier: ^1.1.0
version: 1.1.0
ts-node: ts-node:
specifier: ^10.9.1 specifier: ^10.9.1
version: 10.9.1(@types/node@18.17.12)(typescript@5.2.2) version: 10.9.1(@types/node@18.17.12)(typescript@5.2.2)
@ -6263,6 +6266,10 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/str2int@1.1.0:
resolution: {integrity: sha512-Eb9eGdySBmA2cQyJaxsiB9a6IJ7t1RCQK6o3pUZf6dTI3CVq9TkMcUCVNoWeonv7mNza3Hcp3cf1j73vEhL4ag==}
dev: false
/streamx@2.15.1: /streamx@2.15.1:
resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==} resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==}
dependencies: dependencies:

View File

@ -54,16 +54,18 @@ model WorkspacesOnUsers {
model Website { model Website {
id String @id @unique @default(cuid()) @db.VarChar(30) id String @id @unique @default(cuid()) @db.VarChar(30)
workspaceId String @db.VarChar(30)
name String @db.VarChar(100) name String @db.VarChar(100)
domain String? @db.VarChar(500) domain String? @db.VarChar(500)
shareId String? @unique @db.VarChar(50) shareId String? @unique @db.VarChar(50)
resetAt DateTime? @db.Timestamptz(6) resetAt DateTime? @db.Timestamptz(6)
workspaceId String @db.VarChar(30) monitorId String? @db.VarChar(30)
createdAt DateTime @default(now()) @db.Timestamptz(6) createdAt DateTime @default(now()) @db.Timestamptz(6)
updatedAt DateTime @updatedAt @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6)
deletedAt DateTime? @db.Timestamptz(6) deletedAt DateTime? @db.Timestamptz(6)
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade) workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
monitor Monitor? @relation(fields: [monitorId], references: [id], onUpdate: Cascade, onDelete: Cascade)
sessions WebsiteSession[] sessions WebsiteSession[]
eventData WebsiteEventData[] eventData WebsiteEventData[]
@ -242,6 +244,7 @@ model Monitor {
workspace Workspace @relation(fields: [workspaceId], references: [id]) workspace Workspace @relation(fields: [workspaceId], references: [id])
websites Website[]
notifications Notification[] notifications Notification[]
events MonitorEvent[] events MonitorEvent[]
datas MonitorData[] datas MonitorData[]

View File

@ -3,18 +3,9 @@ import { DateUnit } from '../../utils/date';
import { queryClient } from '../cache'; import { queryClient } from '../cache';
import { request } from '../request'; import { request } from '../request';
import { getUserTimezone } from './user'; import { getUserTimezone } from './user';
import { Website as WebsiteInfo } from '@prisma/client';
export interface WebsiteInfo { export type { WebsiteInfo };
id: string;
name: string;
domain: string | null;
shareId: string | null;
resetAt: string | null;
workspaceId: string;
createdAt: string | null;
updatedAt: string | null;
deletedAt: string | null;
}
export async function getWorkspaceWebsites( export async function getWorkspaceWebsites(
workspaceId: string workspaceId: string

View File

@ -0,0 +1,29 @@
import { Tag } from 'antd';
import React, { useMemo } from 'react';
import str2int from 'str2int';
const builtinColors = [
'magenta',
'red',
'volcano',
'orange',
'gold',
'lime',
'green',
'cyan',
'blue',
'geekblue',
'purple',
];
export const ColorTag: React.FC<{ label: string; colors?: string[] }> =
React.memo((props) => {
const { label, colors = builtinColors } = props;
const color = useMemo(
() => colors[str2int(label) % colors.length],
[label]
);
return <Tag color={color}>{label}</Tag>;
});
ColorTag.displayName = 'ColorTag';

View File

@ -10,6 +10,7 @@ import { useCurrentWorkspaceId } from '../store/user';
import { ErrorTip } from './ErrorTip'; import { ErrorTip } from './ErrorTip';
import { Loading } from './Loading'; import { Loading } from './Loading';
import { NoWorkspaceTip } from './NoWorkspaceTip'; import { NoWorkspaceTip } from './NoWorkspaceTip';
import { MonitorPicker } from './monitor/MonitorPicker';
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '../api/trpc'; import { defaultErrorHandler, defaultSuccessHandler, trpc } from '../api/trpc';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { useEvent } from '../hooks/useEvent'; import { useEvent } from '../hooks/useEvent';
@ -35,12 +36,13 @@ export const WebsiteInfo: React.FC = React.memo(() => {
}); });
const handleSave = useEvent( const handleSave = useEvent(
async (values: { name: string; domain: string }) => { async (values: { name: string; domain: string; monitorId: string }) => {
await updateMutation.mutateAsync({ await updateMutation.mutateAsync({
workspaceId, workspaceId,
websiteId: websiteId!, websiteId: websiteId!,
name: values.name, name: values.name,
domain: values.domain, domain: values.domain,
monitorId: values.monitorId,
}); });
} }
); );
@ -84,6 +86,7 @@ export const WebsiteInfo: React.FC = React.memo(() => {
id: website.id, id: website.id,
name: website.name, name: website.name,
domain: website.domain, domain: website.domain,
monitorId: website.monitorId,
}} }}
onFinish={handleSave} onFinish={handleSave}
> >
@ -101,6 +104,10 @@ export const WebsiteInfo: React.FC = React.memo(() => {
<Input size="large" /> <Input size="large" />
</Form.Item> </Form.Item>
<Form.Item label="Monitor" name="monitorId">
<MonitorPicker size="large" allowClear={true} />
</Form.Item>
<Form.Item> <Form.Item>
<Button size="large" htmlType="submit"> <Button size="large" htmlType="submit">
Save Save

View File

@ -1,4 +1,4 @@
import { Card, Select, Space, Tag } from 'antd'; import { Card, Select, Space } from 'antd';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { trpc } from '../../api/trpc'; import { trpc } from '../../api/trpc';
@ -12,6 +12,7 @@ import { MonitorHealthBar } from './MonitorHealthBar';
import { useSocketSubscribeList } from '../../api/socketio'; import { useSocketSubscribeList } from '../../api/socketio';
import { last, uniqBy } from 'lodash-es'; import { last, uniqBy } from 'lodash-es';
import { ErrorTip } from '../ErrorTip'; import { ErrorTip } from '../ErrorTip';
import { ColorTag } from '../ColorTag';
interface MonitorInfoProps { interface MonitorInfoProps {
monitorId: string; monitorId: string;
@ -42,7 +43,7 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
<div className="text-2xl">{monitorInfo.name}</div> <div className="text-2xl">{monitorInfo.name}</div>
<div> <div>
<Tag color="cyan">{monitorInfo.type}</Tag> <ColorTag label={monitorInfo.type} />
<span> <span>
{getMonitorLink(monitorInfo as any as MonitorInfoType)} {getMonitorLink(monitorInfo as any as MonitorInfoType)}
</span> </span>

View File

@ -0,0 +1,27 @@
import { Select, SelectProps } from 'antd';
import React from 'react';
import { trpc } from '../../api/trpc';
import { useCurrentWorkspaceId } from '../../store/user';
import { ColorTag } from '../ColorTag';
interface MonitorPickerProps extends SelectProps<string> {}
export const MonitorPicker: React.FC<MonitorPickerProps> = React.memo(
(props) => {
const workspaceId = useCurrentWorkspaceId();
const { data: allMonitor = [] } = trpc.monitor.all.useQuery({
workspaceId,
});
return (
<Select {...props}>
{allMonitor.map((m) => (
<Select.Option key={m.id} value={m.id}>
<ColorTag label={m.type} />
{m.name}
</Select.Option>
))}
</Select>
);
}
);
MonitorPicker.displayName = 'MonitorPicker';

View File

@ -24,6 +24,7 @@ import { formatNumber, formatShortTime } from '../../utils/common';
import { useTheme } from '../../hooks/useTheme'; import { useTheme } from '../../hooks/useTheme';
import { WebsiteOnlineCount } from '../WebsiteOnlineCount'; import { WebsiteOnlineCount } from '../WebsiteOnlineCount';
import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate'; import { useGlobalRangeDate } from '../../hooks/useGlobalRangeDate';
import { MonitorHealthBar } from '../monitor/MonitorHealthBar';
export const WebsiteOverview: React.FC<{ export const WebsiteOverview: React.FC<{
website: WebsiteInfo; website: WebsiteInfo;
@ -81,11 +82,9 @@ export const WebsiteOverview: React.FC<{
{props.website.name} {props.website.name}
</span> </span>
<HealthBar {props.website.monitorId && (
beats={Array.from({ length: 13 }).map(() => ({ <MonitorHealthBar monitorId={props.website.monitorId} />
status: 'health', )}
}))}
/>
<div className="ml-4 text-base font-normal"> <div className="ml-4 text-base font-normal">
<WebsiteOnlineCount <WebsiteOnlineCount

View File

@ -157,13 +157,14 @@ export const websiteRouter = router({
updateInfo: workspaceOwnerProcedure updateInfo: workspaceOwnerProcedure
.input( .input(
z.object({ z.object({
websiteId: z.string().cuid(), websiteId: z.string().cuid2(),
name: z.string().max(100), name: z.string().max(100),
domain: z.union([z.string().max(500).url(), z.string().max(500).ip()]), domain: z.union([z.string().max(500).url(), z.string().max(500).ip()]),
monitorId: z.string().cuid2(),
}) })
) )
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const { workspaceId, websiteId, name, domain } = input; const { workspaceId, websiteId, name, domain, monitorId } = input;
const websiteInfo = await prisma.website.update({ const websiteInfo = await prisma.website.update({
where: { where: {
@ -173,6 +174,7 @@ export const websiteRouter = router({
data: { data: {
name, name,
domain, domain,
monitorId,
}, },
}); });