feat: add custom monitor test code
This commit is contained in:
parent
ae6cea568b
commit
3e30535187
@ -1,9 +1,61 @@
|
|||||||
import { Button, Form } from 'antd';
|
import { Button, Form, Modal } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MonitorProvider } from './types';
|
import { MonitorProvider } from './types';
|
||||||
import { CodeEditor } from '../../CodeEditor';
|
import { CodeEditor } from '../../CodeEditor';
|
||||||
|
import { defaultErrorHandler, trpc } from '../../../api/trpc';
|
||||||
|
import { PlayCircleOutlined } from '@ant-design/icons';
|
||||||
|
import { useCurrentWorkspaceId } from '../../../store/user';
|
||||||
|
import { useEvent } from '../../../hooks/useEvent';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { ColorTag } from '../../ColorTag';
|
||||||
|
|
||||||
export const MonitorCustom: React.FC = React.memo(() => {
|
export const MonitorCustom: React.FC = React.memo(() => {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const testScriptMutation = trpc.monitor.testCustomScript.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
|
||||||
|
const handleTestCode = useEvent(async () => {
|
||||||
|
const { logger, result, usage } = await testScriptMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
code: form.getFieldValue(['payload', 'code']),
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.info({
|
||||||
|
centered: true,
|
||||||
|
maskClosable: true,
|
||||||
|
title: 'Run Completed',
|
||||||
|
content: (
|
||||||
|
<div>
|
||||||
|
{logger.map(([type, time, ...args]) => (
|
||||||
|
<div className="mb-1">
|
||||||
|
{type === 'warn' ? (
|
||||||
|
<ColorTag label={type} colors={['orange']} />
|
||||||
|
) : type === 'error' ? (
|
||||||
|
<ColorTag label={type} colors={['red']} />
|
||||||
|
) : (
|
||||||
|
<ColorTag label={type} colors={['geekblue']} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span className="mr-1 text-neutral-500">
|
||||||
|
{dayjs(time).format('HH:mm:ss')}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{args.join(' ')}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div>Usage: {usage}ms</div>
|
||||||
|
<div>
|
||||||
|
Result: <span className="font-semibold">{result}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -13,6 +65,14 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
|||||||
>
|
>
|
||||||
<CodeEditor height={320} />
|
<CodeEditor height={320} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
loading={testScriptMutation.isLoading}
|
||||||
|
icon={<PlayCircleOutlined />}
|
||||||
|
onClick={handleTestCode}
|
||||||
|
>
|
||||||
|
Test Code
|
||||||
|
</Button>
|
||||||
|
{contextHolder}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -13,17 +13,18 @@ export const custom: MonitorProvider<{
|
|||||||
|
|
||||||
const { code } = monitor.payload;
|
const { code } = monitor.payload;
|
||||||
|
|
||||||
const res = await runCodeInVM(code);
|
const { result } = await runCodeInVM(code);
|
||||||
|
|
||||||
if (typeof res !== 'number') {
|
if (typeof result !== 'number') {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return result;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function runCodeInVM(_code: string) {
|
export async function runCodeInVM(_code: string) {
|
||||||
|
const start = Date.now();
|
||||||
const isolate = new ivm.Isolate({ memoryLimit: env.sandboxMemoryLimit });
|
const isolate = new ivm.Isolate({ memoryLimit: env.sandboxMemoryLimit });
|
||||||
|
|
||||||
const code = `${environmentScript}\n\n;(async () => {${_code}})()`;
|
const code = `${environmentScript}\n\n;(async () => {${_code}})()`;
|
||||||
@ -33,7 +34,21 @@ async function runCodeInVM(_code: string) {
|
|||||||
isolate.compileScript(code),
|
isolate.compileScript(code),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
buildSandbox(context);
|
const logger: any[][] = [];
|
||||||
|
|
||||||
|
buildSandbox(context, {
|
||||||
|
console: {
|
||||||
|
log: (...args: any[]) => {
|
||||||
|
logger.push(['log', Date.now(), ...args]);
|
||||||
|
},
|
||||||
|
warn: (...args: any[]) => {
|
||||||
|
logger.push(['warn', Date.now(), ...args]);
|
||||||
|
},
|
||||||
|
error: (...args: any[]) => {
|
||||||
|
logger.push(['error', Date.now(), ...args]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const res = await script.run(context, {
|
const res = await script.run(context, {
|
||||||
promise: true,
|
promise: true,
|
||||||
@ -42,5 +57,5 @@ async function runCodeInVM(_code: string) {
|
|||||||
context.release();
|
context.release();
|
||||||
script.release();
|
script.release();
|
||||||
|
|
||||||
return res;
|
return { logger, result: res, usage: Date.now() - start };
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
import { OPENAPI_TAG } from '../../utils/const';
|
import { OPENAPI_TAG } from '../../utils/const';
|
||||||
import { OpenApiMeta } from 'trpc-openapi';
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
import { MonitorStatusPageModelSchema } from '../../../../prisma/zod';
|
import { MonitorStatusPageModelSchema } from '../../../../prisma/zod';
|
||||||
|
import { runCodeInVM } from '../../model/monitor/provider/custom';
|
||||||
|
|
||||||
export const monitorRouter = router({
|
export const monitorRouter = router({
|
||||||
all: workspaceProcedure
|
all: workspaceProcedure
|
||||||
@ -172,6 +173,22 @@ export const monitorRouter = router({
|
|||||||
|
|
||||||
return monitorManager.delete(workspaceId, monitorId);
|
return monitorManager.delete(workspaceId, monitorId);
|
||||||
}),
|
}),
|
||||||
|
testCustomScript: workspaceOwnerProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
code: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
logger: z.array(z.array(z.any())),
|
||||||
|
result: z.number(),
|
||||||
|
usage: z.number(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return runCodeInVM(input.code);
|
||||||
|
}),
|
||||||
data: workspaceProcedure
|
data: workspaceProcedure
|
||||||
.meta(
|
.meta(
|
||||||
buildMonitorOpenapi({
|
buildMonitorOpenapi({
|
||||||
|
@ -125,11 +125,30 @@ function makeTransferable(data: any) {
|
|||||||
: new ivm.ExternalCopy(copyObject(data)).copyInto();
|
: new ivm.ExternalCopy(copyObject(data)).copyInto();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildSandbox(context: Context) {
|
interface SandboxGlobals {
|
||||||
|
console?: {
|
||||||
|
log: (...args: any[]) => void;
|
||||||
|
warn: (...args: any[]) => void;
|
||||||
|
error: (...args: any[]) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultSandboxGlobals = {
|
||||||
|
console: {
|
||||||
|
log: () => {},
|
||||||
|
warn: () => {},
|
||||||
|
error: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function buildSandbox(context: Context, globals: SandboxGlobals = {}) {
|
||||||
const jail = context.global;
|
const jail = context.global;
|
||||||
jail.setSync('global', jail.derefInto());
|
jail.setSync('global', jail.derefInto());
|
||||||
jail.setSync('ivm', ivm);
|
jail.setSync('_ivm', ivm);
|
||||||
jail.setSync('console', makeTransferable(console));
|
jail.setSync(
|
||||||
|
'console',
|
||||||
|
makeTransferable(globals.console ?? defaultSandboxGlobals)
|
||||||
|
);
|
||||||
jail.setSync(
|
jail.setSync(
|
||||||
'_request',
|
'_request',
|
||||||
new ivm.Reference(async (config: AxiosRequestConfig) => {
|
new ivm.Reference(async (config: AxiosRequestConfig) => {
|
||||||
@ -153,7 +172,7 @@ const reproxy = (reference) => {
|
|||||||
|
|
||||||
const data = reference.get(p);
|
const data = reference.get(p);
|
||||||
|
|
||||||
if (typeof data === 'object' && data instanceof ivm.Reference && data.typeof === 'function') {
|
if (typeof data === 'object' && data instanceof _ivm.Reference && data.typeof === 'function') {
|
||||||
return (...args) => data.apply(undefined, args, { arguments: { copy: true }, result: { promise: true } });
|
return (...args) => data.apply(undefined, args, { arguments: { copy: true }, result: { promise: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user