feat: add monitor bind website
This commit is contained in:
parent
df7d18c2a0
commit
194103eb2d
@ -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",
|
||||||
|
@ -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:
|
||||||
|
@ -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[]
|
||||||
|
@ -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
|
||||||
|
29
src/client/components/ColorTag.tsx
Normal file
29
src/client/components/ColorTag.tsx
Normal 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';
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
27
src/client/components/monitor/MonitorPicker.tsx
Normal file
27
src/client/components/monitor/MonitorPicker.tsx
Normal 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';
|
@ -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
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user