feat: add tcp port monitor
This commit is contained in:
parent
e6f02677e5
commit
9892d3ac5a
@ -82,6 +82,7 @@
|
||||
"socket.io-client": "^4.7.2",
|
||||
"str2int": "^1.1.0",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"tcp-ping": "^0.1.1",
|
||||
"trpc-openapi": "^1.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"uuid": "^9.0.0",
|
||||
@ -116,6 +117,7 @@
|
||||
"@types/request-ip": "^0.0.38",
|
||||
"@types/swagger-ui-express": "^4.1.5",
|
||||
"@types/tar": "^6.1.5",
|
||||
"@types/tcp-ping": "^0.1.5",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"cross-env": "^7.0.3",
|
||||
|
@ -181,6 +181,9 @@ dependencies:
|
||||
swagger-ui-express:
|
||||
specifier: ^5.0.0
|
||||
version: 5.0.0(express@4.18.2)
|
||||
tcp-ping:
|
||||
specifier: ^0.1.1
|
||||
version: 0.1.1
|
||||
trpc-openapi:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(@trpc/server@10.38.4)(zod@3.22.2)
|
||||
@ -279,6 +282,9 @@ devDependencies:
|
||||
'@types/tar':
|
||||
specifier: ^6.1.5
|
||||
version: 6.1.5
|
||||
'@types/tcp-ping':
|
||||
specifier: ^0.1.5
|
||||
version: 0.1.5
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.0.4
|
||||
version: 4.0.4(vite@4.4.9)
|
||||
@ -2749,6 +2755,10 @@ packages:
|
||||
minipass: 4.2.8
|
||||
dev: true
|
||||
|
||||
/@types/tcp-ping@0.1.5:
|
||||
resolution: {integrity: sha512-79CSV6HXSi53zB7JwEpDMIPa881n7drC+Ed1JtQ5kdVUklYyG1g4GqefuUQy/AblK58Q5JAS7d9LWbdE2xiEqA==}
|
||||
dev: true
|
||||
|
||||
/@types/triple-beam@1.3.4:
|
||||
resolution: {integrity: sha512-HlJjF3wxV4R2VQkFpKe0YqJLilYNgtRtsqqZtby7RkVsSs+i+vbyzjtUwpFEdUCKcrGzCiEJE7F/0mKjh0sunA==}
|
||||
dev: false
|
||||
@ -9429,6 +9439,10 @@ packages:
|
||||
yallist: 4.0.0
|
||||
dev: true
|
||||
|
||||
/tcp-ping@0.1.1:
|
||||
resolution: {integrity: sha512-7Ed10Ds0hYnF+O1lfiZ2iSZ1bCAj+96Madctebmq7Y1ALPWlBY4YI8C6pCL+UTlshFY5YogixKLpgDP/4BlHrw==}
|
||||
dev: false
|
||||
|
||||
/temp-dir@1.0.0:
|
||||
resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
||||
import type { Monitor } from '@prisma/client';
|
||||
import { Button, Form, Input, InputNumber, Select } from 'antd';
|
||||
import { getMonitorProvider, monitorProviders } from './provider';
|
||||
import { useEvent } from '../../hooks/useEvent';
|
||||
import { useEvent, useEventWithLoading } from '../../hooks/useEvent';
|
||||
import { NotificationPicker } from '../notification/NotificationPicker';
|
||||
|
||||
export type MonitorInfoEditorValues = Omit<
|
||||
@ -23,7 +23,7 @@ const defaultValues: Omit<MonitorInfoEditorValues, 'payload'> = {
|
||||
|
||||
interface MonitorInfoEditorProps {
|
||||
initialValues?: MonitorInfoEditorValues;
|
||||
onSave: (value: MonitorInfoEditorValues) => void;
|
||||
onSave: (value: MonitorInfoEditorValues) => Promise<void>;
|
||||
}
|
||||
export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
||||
(props) => {
|
||||
@ -46,8 +46,8 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
||||
return <Component />;
|
||||
}, [provider]);
|
||||
|
||||
const handleSubmit = useEvent((values) => {
|
||||
props.onSave({
|
||||
const [handleSubmit, isLoading] = useEventWithLoading(async (values) => {
|
||||
await props.onSave({
|
||||
...values,
|
||||
active: true,
|
||||
});
|
||||
@ -96,7 +96,7 @@ export const MonitorInfoEditor: React.FC<MonitorInfoEditorProps> = React.memo(
|
||||
<NotificationPicker allowClear={true} mode="multiple" />
|
||||
</Form.Item>
|
||||
|
||||
<Button type="primary" htmlType="submit">
|
||||
<Button type="primary" htmlType="submit" loading={isLoading}>
|
||||
Save
|
||||
</Button>
|
||||
</Form>
|
||||
|
@ -5,11 +5,13 @@ import { httpProvider } from './http';
|
||||
import { MonitorProvider } from './types';
|
||||
import { openaiProvider } from './openai';
|
||||
import { customProvider } from './custom';
|
||||
import { tcpProvider } from './tcp';
|
||||
|
||||
export const monitorProviders: MonitorProvider[] = [
|
||||
pingProvider, // ping
|
||||
tcpProvider, // tcp
|
||||
httpProvider, // http
|
||||
openaiProvider, // http
|
||||
openaiProvider, // openai
|
||||
customProvider, // custom node script
|
||||
];
|
||||
|
||||
|
43
src/client/components/monitor/provider/tcp.tsx
Normal file
43
src/client/components/monitor/provider/tcp.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { Form, Input, InputNumber } from 'antd';
|
||||
import React from 'react';
|
||||
import { MonitorProvider } from './types';
|
||||
import { hostnameValidator, portValidator } from '../../../utils/validator';
|
||||
|
||||
export const MonitorTCP: React.FC = React.memo(() => {
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label="Host"
|
||||
name={['payload', 'hostname']}
|
||||
rules={[
|
||||
{ required: true },
|
||||
{
|
||||
validator: hostnameValidator,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="example.com or 1.2.3.4" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Host"
|
||||
name={['payload', 'port']}
|
||||
rules={[
|
||||
{ required: true },
|
||||
{
|
||||
validator: portValidator,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber placeholder="80" min={1} max={65535} />
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
});
|
||||
MonitorTCP.displayName = 'MonitorTCP';
|
||||
|
||||
export const tcpProvider: MonitorProvider = {
|
||||
label: 'TCP Port',
|
||||
name: 'tcp',
|
||||
link: (info) => `${info.payload.hostname}:${info.payload.port}`,
|
||||
form: MonitorTCP,
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
|
||||
// From https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useMemoizedFn/index.ts
|
||||
|
||||
@ -33,3 +33,23 @@ export function useEvent<T extends Noop>(fn: T) {
|
||||
|
||||
return memoizedFn.current as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same with useEvent but return loading state
|
||||
*/
|
||||
export function useEventWithLoading<T extends (...args: any[]) => Promise<any>>(
|
||||
fn: T
|
||||
): [T, boolean] {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const _fn = useEvent(async (...args: Parameters<T>) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
return await fn(...args);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}) as T;
|
||||
|
||||
return [_fn as T, isLoading];
|
||||
}
|
||||
|
@ -25,3 +25,12 @@ export const urlSlugValidator: Validator = (rule, value, callback) => {
|
||||
callback('Not valid slug');
|
||||
}
|
||||
};
|
||||
|
||||
export const portValidator: Validator = (rule, value, callback) => {
|
||||
try {
|
||||
z.number().min(1).max(65535).parse(value);
|
||||
callback();
|
||||
} catch (err) {
|
||||
callback('Not valid port, it should be 1 ~ 65535');
|
||||
}
|
||||
};
|
||||
|
@ -3,10 +3,12 @@ import { ping } from './ping';
|
||||
import { openai } from './openai';
|
||||
import type { MonitorProvider } from './type';
|
||||
import { custom } from './custom';
|
||||
import { tcp } from './tcp';
|
||||
|
||||
export const monitorProviders: Record<string, MonitorProvider<any>> = {
|
||||
ping,
|
||||
http,
|
||||
tcp,
|
||||
openai,
|
||||
custom,
|
||||
};
|
||||
|
42
src/server/model/monitor/provider/tcp.ts
Normal file
42
src/server/model/monitor/provider/tcp.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { MonitorProvider } from './type';
|
||||
import tcpp from 'tcp-ping';
|
||||
|
||||
export const tcp: MonitorProvider<{
|
||||
hostname: string;
|
||||
port: number;
|
||||
}> = {
|
||||
run: async (monitor) => {
|
||||
if (typeof monitor.payload !== 'object') {
|
||||
throw new Error('monitor.payload should be object');
|
||||
}
|
||||
|
||||
const { hostname, port } = monitor.payload;
|
||||
|
||||
const res = await pingAction(hostname, port);
|
||||
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
function pingAction(hostname: string, port: number) {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
tcpp.ping(
|
||||
{
|
||||
address: hostname,
|
||||
port,
|
||||
attempts: 1,
|
||||
},
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (result.results.length >= 1 && result.results[0].err) {
|
||||
reject(result.results[0].err);
|
||||
}
|
||||
|
||||
resolve(Math.round(result.max));
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user