Преглед изворни кода

feat(protocol): add protocol debug

Lind пре 4 година
родитељ
комит
b210e8b67b

+ 2 - 0
src/components/BaseCrud/save/index.tsx

@@ -27,6 +27,7 @@ import { CurdModel } from '@/components/BaseCrud/model';
 import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ModalProps } from 'antd/lib/modal/Modal';
 import FUpload from '@/components/Upload';
+import FMonacoEditor from '@/components/FMonacoEditor';
 
 interface Props<T> {
   schema: ISchema;
@@ -74,6 +75,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
       Editable,
       NumberPicker,
       FUpload,
+      FMonacoEditor,
     },
     scope: {
       icon(name: any) {

+ 1 - 1
src/components/Upload/index.tsx

@@ -66,7 +66,7 @@ const FUpload = connect((props: Props) => {
     node: (
       <>
         <Input
-          value={(url as FileProperty).url}
+          value={(url as FileProperty)?.url}
           onClick={(e) => {
             e.preventDefault();
             e.stopPropagation();

+ 188 - 0
src/pages/link/Protocol/Debug/index.tsx

@@ -0,0 +1,188 @@
+import { Button, Modal } from 'antd';
+import type { ISchema } from '@formily/json-schema';
+import type { Field } from '@formily/core';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Input, Radio, Select, FormItem, Form, Space } from '@formily/antd';
+import FMonacoEditor from '@/components/FMonacoEditor';
+import type { ProtocolItem } from '@/pages/link/Protocol/typings';
+import { service } from '@/pages/link/Protocol';
+import { useEffect, useState } from 'react';
+
+interface Props {
+  close: () => void;
+  data: Partial<ProtocolItem>;
+}
+
+const Debug = (props: Props) => {
+  const { close, data } = props;
+
+  const [transport, setTransport] = useState<{ label: string; value: string }[]>([]);
+  const form = createForm({
+    initialValues: {},
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Select,
+      Radio,
+      Space,
+      FMonacoEditor,
+    },
+    scope: {
+      setDefaultCode(field: Field) {
+        const type = (field.query('.type').take() as Field).value;
+        const trans = (field.query('.transport').take() as Field).value;
+
+        if (type === 'encode') {
+          field.value =
+            '{\n' +
+            '  "messageType":"READ_PROPERTY",\n' +
+            '  "properties":[\n' +
+            '    \n' +
+            '  ]\n' +
+            '}';
+        } else {
+          field.setComponentProps({
+            language: 'text',
+          });
+          switch (trans) {
+            case 'HTTP':
+              field.value = 'POST /url\n' + 'Content-Type: application/json\n' + '\n' + '{}';
+              break;
+            case 'MQTT':
+              field.value = 'QoS0 /topic\n' + '\n' + '{}';
+              break;
+            case 'CoAP':
+              field.value = 'POST /url\n' + 'Content-Format: application/json\n' + '\n' + '{}';
+              break;
+            default:
+              field.value = '';
+              break;
+          }
+        }
+      },
+    },
+  });
+
+  // 获取调试类型
+  const getTransport = () => service.covert(data);
+
+  useEffect(() => {
+    getTransport().then((resp) => {
+      const convertData = resp.result?.transports?.map((item: Record<string, unknown>) => ({
+        label: item.name,
+        value: item.id,
+      }));
+      setTransport(convertData);
+    });
+  }, []);
+
+  const debug = async () => {
+    const values = (await form.submit()) as Record<string, any>;
+    const resp = await service.debug(values.type, {
+      entity: props.data,
+      request: values,
+    });
+    form.setValues({
+      output: resp.result,
+    });
+  };
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      space: {
+        type: 'void',
+        'x-decorator': 'FormItem',
+        'x-component': 'Space',
+        properties: {
+          type: {
+            'x-component': 'Radio.Group',
+            'x-decorator': 'FormItem',
+            enum: [
+              { label: '编码', value: 'encode' },
+              { label: '解码', value: 'decode' },
+            ],
+            default: 'encode',
+          },
+          transport: {
+            title: '链接类型',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-component-props': {
+              style: {
+                width: '200px',
+              },
+            },
+            default: transport ? transport[0]?.value : null,
+            enum: transport,
+          },
+          payloadType: {
+            title: '消息类型',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              style: {
+                width: '200px',
+              },
+            },
+            default: 'JSON',
+            enum: [
+              { label: 'JSON', value: 'JSON' },
+              { label: 'STRING', value: 'STRING' },
+              { label: 'HEX', value: 'HEX' },
+              { label: 'BINARY', value: 'BINARY' },
+            ],
+          },
+        },
+      },
+      payload: {
+        title: '输入',
+        'x-decorator': 'FormItem',
+        'x-component': 'FMonacoEditor',
+        'x-component-props': {
+          height: 200,
+          theme: 'dark',
+          language: 'json',
+          editorDidMount: (editor1: any) => {
+            editor1.onDidContentSizeChange?.(() => {
+              editor1.getAction('editor.action.formatDocument').run();
+            });
+          },
+        },
+        'x-reactions': '{{setDefaultCode}}',
+      },
+      output: {
+        title: '输出',
+        'x-decorator': 'FormItem',
+        'x-component': 'FMonacoEditor',
+        'x-component-props': {
+          height: 200,
+          language: 'verilog',
+        },
+      },
+    },
+  };
+
+  return (
+    <Modal
+      title="调试"
+      width="60vw"
+      onCancel={() => close()}
+      visible={true}
+      footer={
+        <Button type="primary" onClick={debug}>
+          执行
+        </Button>
+      }
+    >
+      <Form form={form} size="small">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default Debug;

+ 255 - 43
src/pages/link/Protocol/index.tsx

@@ -1,23 +1,39 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import BaseService from '@/utils/BaseService';
 import type { ProtocolItem } from '@/pages/link/Protocol/typings';
-import { useRef } from 'react';
+import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import { Tooltip } from 'antd';
+import { message, Popconfirm, Tag, Tooltip } from 'antd';
 import {
-  ArrowDownOutlined,
-  BarsOutlined,
   BugOutlined,
+  CloseOutlined,
+  CloudSyncOutlined,
   EditOutlined,
   MinusOutlined,
+  PlayCircleOutlined,
 } from '@ant-design/icons';
 import BaseCrud from '@/components/BaseCrud';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ISchema } from '@formily/json-schema';
+import { CurdModel } from '@/components/BaseCrud/model';
+import Service from '@/pages/link/Protocol/service';
+import Debug from '@/pages/link/Protocol/Debug';
 
-export const service = new BaseService<ProtocolItem>('protocol');
+export const service = new Service('protocol');
 const Protocol = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<Partial<ProtocolItem>>({});
+
+  const modifyState = async (id: string, type: 'deploy' | 'un-deploy') => {
+    const resp = await service.modifyState(id, type);
+    if (resp.status === 200) {
+      message.success('操作成功!');
+    } else {
+      message.error('操作失败!');
+    }
+    actionRef.current?.reload();
+  };
 
   const columns: ProColumns<ProtocolItem>[] = [
     {
@@ -26,6 +42,12 @@ const Protocol = () => {
       width: 48,
     },
     {
+      dataIndex: 'id',
+      title: 'ID',
+      sorter: true,
+      defaultSortOrder: 'ascend',
+    },
+    {
       dataIndex: 'name',
       title: intl.formatMessage({
         id: 'pages.table.name',
@@ -33,11 +55,14 @@ const Protocol = () => {
       }),
     },
     {
+      dataIndex: 'state',
+      title: '状态',
+      renderText: (text) =>
+        text === 1 ? <Tag color="#108ee9">正常</Tag> : <Tag color="#F50">禁用</Tag>,
+    },
+    {
       dataIndex: 'type',
-      title: intl.formatMessage({
-        id: 'pages.link.type',
-        defaultMessage: '类型',
-      }),
+      title: '类型',
     },
     {
       dataIndex: 'provider',
@@ -55,7 +80,13 @@ const Protocol = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a onClick={() => console.log(record)}>
+        <a
+          key="edit"
+          onClick={() => {
+            CurdModel.update(record);
+            CurdModel.model = 'edit';
+          }}
+        >
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -65,27 +96,40 @@ const Protocol = () => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove',
-              defaultMessage: '删除',
-            })}
-          >
-            <MinusOutlined />
-          </Tooltip>
-        </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.download',
-              defaultMessage: '下载配置',
-            })}
-          >
-            <ArrowDownOutlined />
-          </Tooltip>
-        </a>,
-        <a>
+        record.state !== 1 && (
+          <a key="publish">
+            <Popconfirm title="发布?" onConfirm={() => modifyState(record.id, 'deploy')}>
+              <Tooltip title="发布">
+                <PlayCircleOutlined />
+              </Tooltip>
+            </Popconfirm>
+          </a>
+        ),
+        record.state === 1 && (
+          <a key="reload">
+            <Popconfirm title="重新发布?" onConfirm={() => modifyState(record.id, 'deploy')}>
+              <Tooltip title="重新发布">
+                <CloudSyncOutlined />
+              </Tooltip>
+            </Popconfirm>
+          </a>
+        ),
+        record.state === 1 && (
+          <a key="unDeploy">
+            <Popconfirm onConfirm={() => modifyState(record.id, 'un-deploy')} title="发布?">
+              <Tooltip title="取消发布">
+                <CloseOutlined />
+              </Tooltip>
+            </Popconfirm>
+          </a>
+        ),
+        <a
+          key="debug"
+          onClick={() => {
+            setVisible(true);
+            setCurrent(record);
+          }}
+        >
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.notice.option.debug',
@@ -95,21 +139,187 @@ const Protocol = () => {
             <BugOutlined />
           </Tooltip>
         </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.record',
-              defaultMessage: '通知记录',
-            })}
-          >
-            <BarsOutlined />
-          </Tooltip>
-        </a>,
+        record.state !== 1 && (
+          <a key="delete">
+            <Popconfirm
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove.tips',
+                defaultMessage: '确认删除?',
+              })}
+              onConfirm={async () => {
+                await service.remove(record.id);
+                message.success(
+                  intl.formatMessage({
+                    id: 'pages.data.option.success',
+                    defaultMessage: '操作成功!',
+                  }),
+                );
+                actionRef.current?.reload();
+              }}
+            >
+              <Tooltip
+                title={intl.formatMessage({
+                  id: 'pages.data.option.remove',
+                  defaultMessage: '删除',
+                })}
+              >
+                <MinusOutlined />
+              </Tooltip>
+            </Popconfirm>
+          </a>
+        ),
       ],
     },
   ];
 
-  const schema = {};
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      layout: {
+        type: 'void',
+        'x-component': 'FormGrid',
+        'x-component-props': {
+          maxColumns: 2,
+          minColumns: 2,
+        },
+        properties: {
+          id: {
+            title: 'ID',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            required: true,
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+          name: {
+            title: '名称',
+            required: true,
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 1,
+            },
+          },
+          type: {
+            title: '类型',
+            'x-component': 'Select',
+            'x-decorator': 'FormItem',
+            required: true,
+            enum: [
+              { label: 'jar', value: 'jar' },
+              { label: 'local', value: 'local' },
+              { label: 'script', value: 'script' },
+            ],
+          },
+          configuration: {
+            type: 'object',
+            properties: {
+              provider: {
+                title: '类名',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['..type'],
+                  fulfill: {
+                    state: {
+                      visible: '{{["jar","local"].includes($deps[0])}}',
+                    },
+                  },
+                },
+              },
+              '{url:location}': {
+                title: '文件地址',
+                'x-component': 'FUpload',
+                'x-decorator': 'FormItem',
+                'x-component-props': {
+                  type: 'file',
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['..type'],
+                  when: '{{$deps[0]==="script"}}',
+                  fulfill: {
+                    state: {
+                      visible: false,
+                    },
+                  },
+                  otherwise: {
+                    state: {
+                      visible: '{{["jar","local"].includes($deps[0])}}',
+                      componentType: '{{$deps[0]==="jar"?"FUpload":"Input"}}',
+                      componentProps: '{{$deps[0]==="jar"?{type:"file"}:{}}}',
+                    },
+                  },
+                },
+              },
+              protocol: {
+                title: '协议标识',
+                'x-component': 'Input',
+                'x-decorator': 'FormItem',
+              },
+              transport: {
+                title: '链接协议',
+                'x-component': 'Select',
+                'x-decorator': 'FormItem',
+                enum: [
+                  { label: 'MQTT', value: 'MQTT' },
+                  { label: 'UDP', value: 'UDP' },
+                  { label: 'CoAP', value: 'CoAP' },
+                  { label: 'TCP', value: 'TCP' },
+                  { label: 'HTTP', value: 'HTTP' },
+                  { label: 'HTTPS', value: 'HTTPS' },
+                ],
+              },
+              script: {
+                title: '脚本',
+                'x-component': 'FMonacoEditor',
+                'x-decorator': 'FormItem',
+                'x-decorator-props': {
+                  gridSpan: 2,
+                  labelCol: 2,
+                  wrapperCol: 22,
+                },
+                default: `//解码,收到设备上行消息时
+codec.decoder(function (context) {
+  var message = context.getMessage();
+  return {
+    messageType:"REPORT_PROPERTY"//消息类型
+  };
+});
+
+//编码读取设备属性消息
+codec.encoder("READ_PROPERTY",function(context){
+  var message = context.getMessage();
+  var properties = message.properties;
+})`,
+                'x-component-props': {
+                  height: 200,
+                  theme: 'dark',
+                  language: 'javascript',
+                  editorDidMount: (editor1: any) => {
+                    editor1.onDidContentSizeChange?.(() => {
+                      editor1.getAction('editor.action.formatDocument').run();
+                    });
+                  },
+                },
+                'x-visible': false,
+                'x-reactions': {
+                  dependencies: ['..type'],
+                  fulfill: {
+                    state: {
+                      visible: '{{$deps[0]==="script"}}',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
 
   return (
     <PageContainer>
@@ -120,9 +330,11 @@ const Protocol = () => {
           id: 'pages.link.protocol',
           defaultMessage: '协议管理',
         })}
+        modelConfig={{ width: '50vw' }}
         schema={schema}
         actionRef={actionRef}
       />
+      {visible && <Debug data={current} close={() => setVisible(!visible)} />}
     </PageContainer>
   );
 };

+ 18 - 0
src/pages/link/Protocol/service.ts

@@ -0,0 +1,18 @@
+import type { ProtocolItem } from '@/pages/link/Protocol/typings';
+import { request } from 'umi';
+import BaseService from '@/utils/BaseService';
+
+class Service extends BaseService<ProtocolItem> {
+  public modifyState = (id: string, action: 'deploy' | 'un-deploy') =>
+    request(`${this.uri}/${id}/_${action}`, {
+      method: 'POST',
+    });
+
+  public covert = (data: Record<string, unknown>) =>
+    request(`${this.uri}/convert`, { method: 'POST', data });
+
+  public debug = (type: 'encode' | 'decode', data: Record<string, unknown>) =>
+    request(`${this.uri}/${type}`, { method: 'POST', data });
+}
+
+export default Service;

+ 0 - 1
src/pages/system/Permission/index.tsx

@@ -153,7 +153,6 @@ const Permission: React.FC = observer(() => {
         <a
           key="editable"
           onClick={() => {
-            console.log(record);
             CurdModel.update(record);
           }}
         >