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 { MonitorProvider } from './types';
|
||||
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(() => {
|
||||
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 (
|
||||
<>
|
||||
<Form.Item
|
||||
@ -13,6 +65,14 @@ export const MonitorCustom: React.FC = React.memo(() => {
|
||||
>
|
||||
<CodeEditor height={320} />
|
||||
</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 res = await runCodeInVM(code);
|
||||
const { result } = await runCodeInVM(code);
|
||||
|
||||
if (typeof res !== 'number') {
|
||||
if (typeof result !== 'number') {
|
||||
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 code = `${environmentScript}\n\n;(async () => {${_code}})()`;
|
||||
@ -33,7 +34,21 @@ async function runCodeInVM(_code: string) {
|
||||
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, {
|
||||
promise: true,
|
||||
@ -42,5 +57,5 @@ async function runCodeInVM(_code: string) {
|
||||
context.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 { OpenApiMeta } from 'trpc-openapi';
|
||||
import { MonitorStatusPageModelSchema } from '../../../../prisma/zod';
|
||||
import { runCodeInVM } from '../../model/monitor/provider/custom';
|
||||
|
||||
export const monitorRouter = router({
|
||||
all: workspaceProcedure
|
||||
@ -172,6 +173,22 @@ export const monitorRouter = router({
|
||||
|
||||
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
|
||||
.meta(
|
||||
buildMonitorOpenapi({
|
||||
|
@ -125,11 +125,30 @@ function makeTransferable(data: any) {
|
||||
: 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;
|
||||
jail.setSync('global', jail.derefInto());
|
||||
jail.setSync('ivm', ivm);
|
||||
jail.setSync('console', makeTransferable(console));
|
||||
jail.setSync('_ivm', ivm);
|
||||
jail.setSync(
|
||||
'console',
|
||||
makeTransferable(globals.console ?? defaultSandboxGlobals)
|
||||
);
|
||||
jail.setSync(
|
||||
'_request',
|
||||
new ivm.Reference(async (config: AxiosRequestConfig) => {
|
||||
@ -153,7 +172,7 @@ const reproxy = (reference) => {
|
||||
|
||||
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 } });
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user