Explorar o código

fix: 修改数据采集

100011797 %!s(int64=3) %!d(string=hai) anos
pai
achega
41a84a6ed0

+ 1 - 0
src/pages/link/DataCollect/components/Device/Save/index.tsx

@@ -188,6 +188,7 @@ export default (props: Props) => {
             ...value,
             provider: resp.result.provider,
             channelId: props.channelId,
+            channelName: resp.result.name,
             configuration: {
               ...value.configuration,
             },

+ 1 - 0
src/pages/link/DataCollect/components/Device/index.tsx

@@ -254,6 +254,7 @@ export default observer((props: Props) => {
                                       defaultMessage: '操作成功!',
                                     }),
                                   );
+                                  handleSearch(param);
                                 } else {
                                   onlyMessage(
                                     intl.formatMessage({ id: 'pages.device.instance.deleteTip' }),

+ 3 - 2
src/pages/link/DataCollect/components/Point/CollectorCard/index.less

@@ -51,12 +51,13 @@
     }
     .card-item-content {
       display: flex;
+      overflow: hidden;
       .card-item-content-item {
         flex-direction: row-reverse;
         align-items: flex-start;
         justify-content: space-around;
-        //min-width: calc(50% - 40px);
-        width: calc(50% - 20px);
+        min-width: calc(50% - 40px);
+        max-width: calc(50% - 20px);
 
         .card-item-content-item-empty {
           margin-top: 10px;

+ 62 - 57
src/pages/link/DataCollect/components/Point/CollectorCard/index.tsx

@@ -1,30 +1,36 @@
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
 import { Ellipsis, PermissionButton } from '@/components';
 import './index.less';
 import { Badge, Popconfirm, Spin, Tooltip } from 'antd';
 import { DeleteOutlined, EditOutlined, FormOutlined, RedoOutlined } from '@ant-design/icons';
-import OpcSave from '../Save/opc-ua';
-import ModbusSave from '../Save/modbus';
+// import OpcSave from '../Save/opc-ua';
+// import ModbusSave from '../Save/modbus';
 import service from '@/pages/link/DataCollect/service';
 import { onlyMessage } from '@/utils/util';
 import moment from 'moment';
-import WritePoint from '@/pages/link/DataCollect/components/Point/CollectorCard/WritePoint';
+// import WritePoint from '@/pages/link/DataCollect/components/Point/CollectorCard/WritePoint';
 
 export interface PointCardProps {
   item: Partial<PointItem>;
   reload: () => void;
   wsValue: any;
+  update: (item: any, type?: boolean) => void;
 }
 
 const opcImage = require('/public/images/DataCollect/device-opcua.png');
 const modbusImage = require('/public/images/DataCollect/device-modbus.png');
 
-export default (props: PointCardProps) => {
+const CollectorCard = (props: PointCardProps) => {
   const { item, wsValue } = props;
-  const [editVisible, setEditVisible] = useState<boolean>(false);
+  // const [editVisible, setEditVisible] = useState<boolean>(false);
   const [spinning, setSpinning] = useState<boolean>(false);
-  const [writeVisible, setWriteVisible] = useState<boolean>(false);
+  // const [writeVisible, setWriteVisible] = useState<boolean>(false);
   const { permission } = PermissionButton.usePermission('link/DataCollect/DataGathering');
+  const [dataValue, setDataValue] = useState<any>(wsValue);
+
+  useEffect(() => {
+    setDataValue(wsValue);
+  }, [wsValue]);
 
   const read = async () => {
     if (item?.collectorId && item?.id) {
@@ -37,33 +43,33 @@ export default (props: PointCardProps) => {
     }
   };
 
-  const saveComponent = () => {
-    if (item.provider === 'OPC_UA') {
-      return (
-        <OpcSave
-          close={() => {
-            setEditVisible(false);
-          }}
-          reload={() => {
-            setEditVisible(false);
-          }}
-          data={item}
-        />
-      );
-    }
-    return (
-      <ModbusSave
-        close={() => {
-          setEditVisible(false);
-        }}
-        collector={{}}
-        reload={() => {
-          setEditVisible(false);
-        }}
-        data={item}
-      />
-    );
-  };
+  // const saveComponent = () => {
+  //   if (item.provider === 'OPC_UA') {
+  //     return (
+  //       <OpcSave
+  //         close={() => {
+  //           setEditVisible(false);
+  //         }}
+  //         reload={() => {
+  //           setEditVisible(false);
+  //         }}
+  //         data={item}
+  //       />
+  //     );
+  //   }
+  //   return (
+  //     <ModbusSave
+  //       close={() => {
+  //         setEditVisible(false);
+  //       }}
+  //       collector={{}}
+  //       reload={() => {
+  //         setEditVisible(false);
+  //       }}
+  //       data={item}
+  //     />
+  //   );
+  // };
   return (
     <Spin spinning={spinning}>
       <div className={'card-item'}>
@@ -113,7 +119,8 @@ export default (props: PointCardProps) => {
                   <FormOutlined
                     onClick={() => {
                       if (permission.update) {
-                        setEditVisible(true);
+                        // setEditVisible(true);
+                        props.update(item);
                       }
                     }}
                   />
@@ -121,17 +128,18 @@ export default (props: PointCardProps) => {
               </div>
             </div>
             <div className={'card-item-content'}>
-              {wsValue ? (
+              {dataValue ? (
                 <div className={'card-item-content-item'}>
                   <div className={'card-item-content-item-header'}>
                     <div className={'card-item-content-item-header-title'}>
-                      <Ellipsis title={`${wsValue?.parseData}(${wsValue?.dataType})`} />
+                      <Ellipsis title={`${dataValue?.parseData}(${dataValue?.dataType})`} />
                     </div>
                     <div className={'card-item-content-item-header-action'}>
                       <EditOutlined
                         style={{ marginRight: 5 }}
                         onClick={() => {
-                          setWriteVisible(true);
+                          props.update(item, true);
+                          // setWriteVisible(true);
                         }}
                       />
                       <RedoOutlined
@@ -142,13 +150,13 @@ export default (props: PointCardProps) => {
                     </div>
                   </div>
                   <div className={'card-item-content-item-text'}>
-                    <Ellipsis title={wsValue?.hex || ''} />
+                    <Ellipsis title={dataValue?.hex || ''} />
                   </div>
                   <div className={'card-item-content-item-text'}>
                     <Ellipsis
                       title={
-                        wsValue?.timestamp
-                          ? moment(wsValue.timestamp).format('YYYY-MM-DD HH:mm:ss')
+                        dataValue?.timestamp
+                          ? moment(dataValue.timestamp).format('YYYY-MM-DD HH:mm:ss')
                           : ''
                       }
                     />
@@ -164,15 +172,10 @@ export default (props: PointCardProps) => {
                       className={'action'}
                       style={{ margin: '0 15px' }}
                       onClick={() => {
-                        setWriteVisible(true);
-                      }}
-                    />
-                    <RedoOutlined
-                      className={'action'}
-                      onClick={() => {
-                        read();
+                        props.update(item, true);
                       }}
                     />
+                    <RedoOutlined className={'action'} onClick={read} />
                   </div>
                 </div>
               )}
@@ -210,16 +213,18 @@ export default (props: PointCardProps) => {
             </div>
           </div>
         </div>
-        {editVisible && saveComponent()}
-        {writeVisible && (
-          <WritePoint
-            data={item}
-            onCancel={() => {
-              setWriteVisible(false);
-            }}
-          />
-        )}
+        {/*{editVisible && saveComponent()}*/}
+        {/*{writeVisible && (*/}
+        {/*  <WritePoint*/}
+        {/*    data={item}*/}
+        {/*    onCancel={() => {*/}
+        {/*      setWriteVisible(false);*/}
+        {/*    }}*/}
+        {/*  />*/}
+        {/*)}*/}
       </div>
     </Spin>
   );
 };
+
+export default CollectorCard;

+ 214 - 0
src/pages/link/DataCollect/components/Point/Save/BatchUpdate.tsx

@@ -0,0 +1,214 @@
+import { Button, Modal } from 'antd';
+import { FormItem, Input, ArrayTable, Editable, NumberPicker } from '@formily/antd';
+import MyInput from './components/MyInput';
+import MySelect from './components/MySelect';
+import { createForm, registerValidateRules } from '@formily/core';
+import { FormProvider, createSchemaField } from '@formily/react';
+import { useEffect } from 'react';
+
+interface Props {
+  data?: any[];
+  close: () => void;
+  reload: () => void;
+}
+
+export default (props: Props) => {
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Editable,
+      Input,
+      ArrayTable,
+      NumberPicker,
+      MyInput,
+      MySelect,
+    },
+  });
+
+  const form = createForm({
+    initialValues: { array: [] },
+  });
+
+  registerValidateRules({
+    checkLength(value) {
+      if (String(value).length > 64) {
+        return {
+          type: 'error',
+          message: '最多可输入64个字符',
+        };
+      }
+      if (!(value % 1 === 0)) {
+        return {
+          type: 'error',
+          message: '请输入非0正整数',
+        };
+      }
+      return '';
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      array: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        'x-component-props': {
+          pagination: { pageSize: 10 },
+          scroll: { x: '100%' },
+        },
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: '名称' },
+              properties: {
+                name: {
+                  type: 'string',
+                  'x-component': 'Input',
+                  'x-component-props': {
+                    placeholder: '请输入点位名称',
+                  },
+                  'x-validator': [
+                    {
+                      required: true,
+                      message: '请输入点位名称',
+                    },
+                    {
+                      max: 64,
+                      message: '最多可输入64个字符',
+                    },
+                  ],
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: 'nodeId', width: 150 },
+              properties: {
+                'configuration.nodeId': {
+                  type: 'string',
+                  'x-component': 'Input',
+                  'x-component-props': {
+                    readOnly: true,
+                  },
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: '访问类型', width: 200 },
+              properties: {
+                accessModes: {
+                  type: 'string',
+                  'x-component': 'MySelect',
+                  'x-component-props': {
+                    placeholder: '请选择访问类型',
+                    mode: 'multiple',
+                    options: [
+                      { label: '读', value: 'read' },
+                      { label: '写', value: 'write' },
+                      { label: '订阅', value: 'subscribe' },
+                    ],
+                  },
+                  'x-validator': [
+                    {
+                      required: true,
+                      message: '请选择访问类型',
+                    },
+                  ],
+                },
+              },
+            },
+            column4: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { title: '采集频率' },
+              properties: {
+                'configuration.interval': {
+                  type: 'string',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'MyInput',
+                  'x-component-props': {
+                    placeholder: '请输入采集频率',
+                  },
+                  'x-validator': [
+                    {
+                      required: true,
+                      message: '请输入采集频率',
+                    },
+                    {
+                      checkLength: true,
+                    },
+                  ],
+                },
+              },
+            },
+            column5: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 210, title: '只推送变化的数据' },
+              properties: {
+                features: {
+                  type: 'array',
+                  'x-decorator': 'FormItem',
+                  'x-component': 'MySelect',
+                  'x-component-props': {
+                    options: [
+                      {
+                        label: '是',
+                        value: true,
+                      },
+                      {
+                        label: '否',
+                        value: false,
+                      },
+                    ],
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  };
+
+  useEffect(() => {
+    form.setValues({
+      array: (props?.data || []).map((item) => {
+        return {
+          ...item,
+          features: item.features.includes('changedOnly'),
+        };
+      }),
+    });
+  }, [props.data]);
+
+  return (
+    <Modal
+      title={'批量编辑'}
+      maskClosable={false}
+      visible
+      onCancel={props.close}
+      width={1200}
+      footer={[
+        <Button key={1} onClick={props.close}>
+          取消
+        </Button>,
+        <Button type="primary" key={2} onClick={() => {}}>
+          确定
+        </Button>,
+      ]}
+    >
+      <FormProvider form={form}>
+        <SchemaField schema={schema} />
+      </FormProvider>
+    </Modal>
+  );
+};

+ 39 - 0
src/pages/link/DataCollect/components/Point/Save/components/MyInput.tsx

@@ -0,0 +1,39 @@
+import { Space, InputNumber, Checkbox } from 'antd';
+import { useState } from 'react';
+import { ArrayItems } from '@formily/antd';
+
+interface Props {
+  value: any;
+  onChange: (data: any) => void;
+  placeholder?: string;
+}
+export default (props: Props) => {
+  const index = ArrayItems.useIndex!();
+  const [edit, setEdit] = useState<boolean>(index > 0);
+  return (
+    <Space>
+      <InputNumber
+        style={{ width: 150 }}
+        defaultValue={3000}
+        placeholder={props.placeholder}
+        value={props.value}
+        onChange={(val) => {
+          props.onChange(val);
+        }}
+        readOnly={edit}
+        addonAfter={'毫秒'}
+      />
+      {index > 0 && (
+        <Checkbox
+          style={{ width: 60 }}
+          onChange={() => {
+            setEdit(!edit);
+          }}
+          checked={edit}
+        >
+          同上
+        </Checkbox>
+      )}
+    </Space>
+  );
+};

+ 43 - 0
src/pages/link/DataCollect/components/Point/Save/components/MySelect.tsx

@@ -0,0 +1,43 @@
+import { Space, Select, Checkbox, SelectProps } from 'antd';
+import { useState } from 'react';
+import { ArrayItems } from '@formily/antd';
+interface Props extends SelectProps {
+  value: any;
+  onChange: (data: any) => void;
+  placeholder?: string;
+  options: any[];
+}
+export default (props: Props) => {
+  const { onChange, ...extra } = props;
+  const [edit, setEdit] = useState<boolean>(true);
+  const index = ArrayItems.useIndex!();
+  return (
+    <Space>
+      <Select
+        style={{ minWidth: 170 }}
+        placeholder={props.placeholder}
+        {...extra}
+        onChange={(val) => {
+          props.onChange(val);
+        }}
+      >
+        {props.options.map((item) => (
+          <Select.Option key={item.value} value={item.value}>
+            {item.label}
+          </Select.Option>
+        ))}
+      </Select>
+      {index > 0 && (
+        <Checkbox
+          style={{ width: 60 }}
+          onChange={() => {
+            setEdit(!edit);
+          }}
+          checked={edit}
+        >
+          同上
+        </Checkbox>
+      )}
+    </Space>
+  );
+};

+ 151 - 42
src/pages/link/DataCollect/components/Point/index.tsx

@@ -12,24 +12,43 @@ import ModbusSave from '@/pages/link/DataCollect/components/Point/Save/modbus';
 import Scan from '@/pages/link/DataCollect/components/Point/Save/scan';
 import { map } from 'rxjs/operators';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
-
+import OpcSave from '@/pages/link/DataCollect/components/Point/Save/opc-ua';
+import WritePoint from '@/pages/link/DataCollect/components/Point/CollectorCard/WritePoint';
+import BatchUpdate from './Save/BatchUpdate';
 interface Props {
   type: boolean; // true: 综合查询  false: 数据采集
   data?: Partial<CollectorItem>;
   provider?: 'OPC_UA' | 'MODBUS_TCP';
 }
 
+interface PointCardProps {
+  type: boolean; // true: 综合查询  false: 数据采集
+  data?: Partial<CollectorItem>;
+  provider?: 'OPC_UA' | 'MODBUS_TCP';
+  reload: boolean; // 变化时刷新
+}
+
 const PointModel = model<{
   m_visible: boolean;
+  p_visible: boolean;
   p_add_visible: boolean;
+  writeVisible: boolean;
   current: Partial<PointItem>;
+  reload: boolean;
+  batch_visible: boolean;
+  list: any[];
 }>({
   m_visible: false,
+  p_visible: false,
   p_add_visible: false,
   current: {},
+  writeVisible: false,
+  reload: false,
+  batch_visible: false,
+  list: [],
 });
 
-export default observer((props: Props) => {
+const PointCard = observer((props: PointCardProps) => {
   const [subscribeTopic] = useSendWebsocketMessage();
   const { minHeight } = useDomFullHeight(`.data-collect-point`, 24);
   const [param, setParam] = useState({ pageSize: 12, terms: [] });
@@ -43,46 +62,72 @@ export default observer((props: Props) => {
     total: 0,
   });
 
-  const columns: ProColumns<PointItem>[] = [
-    {
-      title: '名称',
-      dataIndex: 'name',
-    },
-    {
-      title: '通讯协议',
-      dataIndex: 'provider',
-      valueType: 'select',
-      valueEnum: {
-        OPC_UA: {
-          text: 'OPC_UA',
-          status: 'OPC_UA',
+  const columns: ProColumns<PointItem>[] = props.type
+    ? [
+        {
+          title: '名称',
+          dataIndex: 'name',
+        },
+        {
+          title: '通讯协议',
+          dataIndex: 'provider',
+          valueType: 'select',
+          valueEnum: {
+            OPC_UA: {
+              text: 'OPC_UA',
+              status: 'OPC_UA',
+            },
+            MODBUS_TCP: {
+              text: 'MODBUS_TCP',
+              status: 'MODBUS_TCP',
+            },
+          },
+        },
+        {
+          title: '状态',
+          dataIndex: 'state',
+          valueType: 'select',
+          valueEnum: {
+            enabled: {
+              text: '正常',
+              status: 'enabled',
+            },
+            disabled: {
+              text: '异常',
+              status: 'disabled',
+            },
+          },
+        },
+        {
+          title: '说明',
+          dataIndex: 'description',
         },
-        MODBUS_TCP: {
-          text: 'MODBUS_TCP',
-          status: 'MODBUS_TCP',
+      ]
+    : [
+        {
+          title: '名称',
+          dataIndex: 'name',
         },
-      },
-    },
-    {
-      title: '状态',
-      dataIndex: 'state',
-      valueType: 'select',
-      valueEnum: {
-        enabled: {
-          text: '正常',
-          status: 'enabled',
+        {
+          title: '状态',
+          dataIndex: 'state',
+          valueType: 'select',
+          valueEnum: {
+            enabled: {
+              text: '正常',
+              status: 'enabled',
+            },
+            disabled: {
+              text: '异常',
+              status: 'disabled',
+            },
+          },
         },
-        disabled: {
-          text: '异常',
-          status: 'disabled',
+        {
+          title: '说明',
+          dataIndex: 'description',
         },
-      },
-    },
-    {
-      title: '说明',
-      dataIndex: 'description',
-    },
-  ];
+      ];
 
   const subRef = useRef<any>(null);
 
@@ -117,7 +162,7 @@ export default observer((props: Props) => {
       .then((resp) => {
         if (resp.status === 200) {
           setDataSource(resp.result);
-          console.log(resp.result);
+          PointModel.list = resp.result?.data || [];
           subscribeProperty((resp.result?.data || []).map((item: any) => item.id));
         }
         setLoading(false);
@@ -126,7 +171,7 @@ export default observer((props: Props) => {
 
   useEffect(() => {
     handleSearch(param);
-  }, [props.data?.id]);
+  }, [props.data?.id, props.reload]);
 
   useEffect(() => {
     return () => {
@@ -168,6 +213,18 @@ export default observer((props: Props) => {
                 >
                   {props?.provider === 'OPC_UA' ? '扫描' : '新增'}
                 </PermissionButton>
+                {props.provider === 'OPC_UA' && (
+                  <PermissionButton
+                    style={{ marginLeft: 15 }}
+                    isPermission={permission.update}
+                    onClick={() => {
+                      PointModel.batch_visible = true;
+                    }}
+                    key="batch"
+                  >
+                    批量编辑
+                  </PermissionButton>
+                )}
               </div>
             )}
             {dataSource?.data.length ? (
@@ -181,6 +238,18 @@ export default observer((props: Props) => {
                         reload={() => {
                           handleSearch(param);
                         }}
+                        update={(item, flag) => {
+                          if (flag) {
+                            PointModel.writeVisible = true;
+                          } else {
+                            if (item.provider === 'MODBUS_TCP') {
+                              PointModel.m_visible = true;
+                            } else {
+                              PointModel.p_visible = true;
+                            }
+                          }
+                          PointModel.current = item;
+                        }}
                       />
                     </Col>
                   ))}
@@ -225,6 +294,14 @@ export default observer((props: Props) => {
           </div>
         </div>
       </Card>
+    </div>
+  );
+});
+
+export default observer((props: Props) => {
+  return (
+    <div>
+      <PointCard {...props} reload={PointModel.reload} />
       {PointModel.m_visible && (
         <ModbusSave
           data={PointModel.current}
@@ -234,8 +311,20 @@ export default observer((props: Props) => {
           }}
           reload={() => {
             PointModel.m_visible = false;
-            handleSearch(param);
+            PointModel.reload = !PointModel.reload;
+          }}
+        />
+      )}
+      {PointModel.p_visible && (
+        <OpcSave
+          close={() => {
+            PointModel.p_visible = false;
+          }}
+          reload={() => {
+            PointModel.p_visible = false;
+            PointModel.reload = !PointModel.reload;
           }}
+          data={PointModel.current}
         />
       )}
       {PointModel.p_add_visible && (
@@ -246,7 +335,27 @@ export default observer((props: Props) => {
           collector={props.data}
           reload={() => {
             PointModel.p_add_visible = false;
-            handleSearch(param);
+            PointModel.reload = !PointModel.reload;
+          }}
+        />
+      )}
+      {PointModel.writeVisible && (
+        <WritePoint
+          data={PointModel.current}
+          onCancel={() => {
+            PointModel.writeVisible = false;
+          }}
+        />
+      )}
+      {PointModel.batch_visible && (
+        <BatchUpdate
+          close={() => {
+            PointModel.batch_visible = false;
+          }}
+          data={PointModel.list}
+          reload={() => {
+            PointModel.batch_visible = false;
+            PointModel.reload = !PointModel.reload;
           }}
         />
       )}

+ 2 - 2
src/pages/link/DataCollect/components/Tree/index.tsx

@@ -111,10 +111,10 @@ export default observer((props: Props) => {
                       </div>
                       <div>
                         <Space className={styles.iconColor}>
-                          <Tooltip title={!permission.edit ? '暂无权限,请联系管理员' : ''}>
+                          <Tooltip title={!permission.update ? '暂无权限,请联系管理员' : ''}>
                             <FormOutlined
                               onClick={() => {
-                                if (permission.edit) {
+                                if (permission.update) {
                                   TreeModel.current = item;
                                   TreeModel.visible = true;
                                 }

+ 66 - 2
src/pages/system/Menu/Setting/baseMenu.ts

@@ -1411,6 +1411,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['save'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save'],
+                      },
                     ],
                   },
                   {
@@ -1419,7 +1427,15 @@ export default [
                     permissions: [
                       {
                         permission: 'data-collect-channel',
-                        actions: ['add', 'query'],
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save', 'query'],
                       },
                     ],
                   },
@@ -1431,6 +1447,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['save', 'query'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save', 'query'],
+                      },
                     ],
                   },
                   {
@@ -1441,6 +1465,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['delete'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['delete'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['delete'],
+                      },
                     ],
                   },
                 ],
@@ -1468,6 +1500,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['save'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save'],
+                      },
                     ],
                   },
                   {
@@ -1476,7 +1516,15 @@ export default [
                     permissions: [
                       {
                         permission: 'data-collect-channel',
-                        actions: ['add', 'query'],
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save', 'query'],
                       },
                     ],
                   },
@@ -1488,6 +1536,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['save', 'query'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['save', 'query'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['save', 'query'],
+                      },
                     ],
                   },
                   {
@@ -1498,6 +1554,14 @@ export default [
                         permission: 'data-collect-channel',
                         actions: ['delete'],
                       },
+                      {
+                        permission: 'data-collector',
+                        actions: ['delete'],
+                      },
+                      {
+                        permission: 'data-collect-opc',
+                        actions: ['delete'],
+                      },
                     ],
                   },
                 ],