Sfoglia il codice sorgente

fix(network): network crud

Lind 3 anni fa
parent
commit
9e3aa70be0

+ 207 - 49
src/pages/link/Type/Save/index.tsx

@@ -1,9 +1,9 @@
 import { PageContainer } from '@ant-design/pro-layout';
-// import { useParams } from 'umi';
-import { createSchemaField } from '@formily/react';
+import { createSchemaField, observer } from '@formily/react';
 import {
   ArrayCollapse,
   Form,
+  FormButtonGroup,
   FormCollapse,
   FormGrid,
   FormItem,
@@ -12,17 +12,19 @@ import {
   Password,
   Radio,
   Select,
+  Submit,
 } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { useEffect, useMemo, useRef } from 'react';
 import type { Field } from '@formily/core';
 import { createForm, onFieldValueChange } from '@formily/core';
-import { Card } from 'antd';
+import { Button, Card, message } from 'antd';
 import styles from './index.less';
 import { useAsyncDataSource } from '@/utils/util';
 import { service } from '..';
 import _ from 'lodash';
 import FAutoComplete from '@/components/FAutoComplete';
+import { Store } from 'jetlinks-store';
 
 /**
  *  根据类型过滤配置信息
@@ -30,34 +32,31 @@ import FAutoComplete from '@/components/FAutoComplete';
  * @param type
  */
 const filterConfigByType = (data: any[], type: string) => {
+  // UDP、TCP_SERVER、WEB_SOCKET_SERVER、HTTP_SERVER、MQTT_SERVER、COAP_SERVER
+
+  const tcpList = ['TCP_SERVER', 'WEB_SOCKET_SERVER', 'HTTP_SERVER', 'MQTT_SERVER'];
+  const udpList = ['UDP', 'COAP_SERVER'];
+
+  let _temp = type;
+  if (tcpList.includes(type)) {
+    _temp = 'TCP';
+  } else if (udpList.includes(type)) {
+    _temp = 'UDP';
+  }
   // 只保留ports 包含type的数据
-  const _config = data.filter((item) => Object.keys(item.ports).includes(type));
+  const _config = data.filter((item) => Object.keys(item.ports).includes(_temp));
   // 只保留ports的type数据
   return _config.map((i) => {
-    i.ports = i.ports[type];
+    i.ports = i.ports[_temp];
     return i;
   });
 };
-const Save = () => {
+const Save = observer(() => {
   // const param = useParams<{ id: string }>();
 
   // const [config, setConfig] = useState<any[]>([]);
   const configRef = useRef([]);
 
-  const getResourcesClusters = () => {
-    // eslint-disable-next-line @typescript-eslint/no-use-before-define
-    const checked = form.getValuesIn('config')?.map((i: any) => i?.nodeName) || [];
-    return service.getResourceClusters().then((resp) => {
-      // 获取到已经选择的节点名称。然后过滤、通过form.values获取
-      return resp.result
-        ?.map((item: any) => ({
-          label: item.name,
-          value: item.id,
-        }))
-        .filter((j: any) => !checked.includes(j.value));
-    });
-  };
-
   useEffect(() => {
     service.getResourcesCurrent().then((resp) => {
       if (resp.status === 200) {
@@ -68,6 +67,26 @@ const Save = () => {
     });
   }, []);
 
+  const getResourcesClusters = () => {
+    // eslint-disable-next-line @typescript-eslint/no-use-before-define
+    const checked = form.getValuesIn('cluster')?.map((i: any) => i?.serverId) || [];
+    // cache resourcesCluster
+    if (Store.get('resources-cluster')?.length > 0) {
+      return new Promise((resolve) => {
+        resolve(Store.get('resources-cluster').filter((j: any) => !checked.includes(j.value)));
+      });
+    } else {
+      return service.getResourceClusters().then((resp) => {
+        const _data = resp.result?.map((item: any) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        Store.set('resources-cluster', _data);
+        return _data.filter((j: any) => !checked.includes(j.value));
+      });
+    }
+  };
+
   const getResourceById = (id: string, type: string) =>
     service.getResourceClustersById(id).then((resp) => filterConfigByType(resp.result, type));
 
@@ -81,7 +100,8 @@ const Save = () => {
   const form = useMemo(
     () =>
       createForm({
-        initialValues: {},
+        readPretty: false,
+        // initialValues: {},
         effects() {
           onFieldValueChange('type', (field, f) => {
             const value = (field as Field).value;
@@ -90,7 +110,7 @@ const Save = () => {
               state.dataSource = _host.map((item) => ({ label: item.host, value: item.host }));
             });
             f.setFieldState('cluster.config.*.host', (state) => {
-              state.dataSource = _host.map((item) => item.host);
+              state.dataSource = _host.map((item) => ({ label: item.host, value: item.host }));
             });
           });
           onFieldValueChange('grid.configuration.panel1.layout2.host', (field, f1) => {
@@ -98,6 +118,7 @@ const Save = () => {
             const type = (field.query('type').take() as Field).value;
             const _port = filterConfigByType(_.cloneDeep(configRef.current), type);
             const _host = _port.find((item) => item.host === value);
+            console.log(_host, 'host');
             f1.setFieldState('grid.configuration.panel1.layout2.port', (state) => {
               state.dataSource = _host?.ports.map((p: any) => ({ label: p, value: p }));
             });
@@ -108,7 +129,7 @@ const Save = () => {
               // false 获取独立配置的信息
             }
           });
-          onFieldValueChange('grid.cluster.config.*.layout2.nodeName', async (field, f3) => {
+          onFieldValueChange('grid.cluster.cluster.*.layout2.serverId', async (field, f3) => {
             const value = (field as Field).value;
             const type = (field.query('type').take() as Field).value;
             const response = await getResourceById(value, type);
@@ -116,9 +137,9 @@ const Save = () => {
               state.dataSource = response.map((item) => ({ label: item.host, value: item.host }));
             });
           });
-          onFieldValueChange('grid.cluster.config.*.layout2.host', async (field, f4) => {
+          onFieldValueChange('grid.cluster.cluster.*.layout2.host', async (field, f4) => {
             const host = (field as Field).value;
-            const value = (field.query('.nodeName').take() as Field).value;
+            const value = (field.query('.serverId').take() as Field).value;
             const type = (field.query('type').take() as Field).value;
             const response = await getResourceById(value, type);
             const _ports = response.find((item) => item.host === host);
@@ -131,6 +152,18 @@ const Save = () => {
     [],
   );
 
+  useEffect(() => {
+    Store.subscribe('current-network-data', (data) => {
+      form.readPretty = true;
+      const _data = _.cloneDeep(data);
+      // 处理一下集群模式数据
+      if (!_data.shareCluster) {
+        _data.cluster = _data.cluster?.map((item: any) => ({ ...item.configuration }));
+      }
+      form.setValues(_data);
+    });
+  }, []);
+
   const SchemaField = createSchemaField({
     components: {
       FormItem,
@@ -165,7 +198,7 @@ const Save = () => {
       columnGap: 48,
     },
     properties: {
-      nodeName: {
+      serverId: {
         title: '节点名称',
         'x-component': 'Select',
         'x-decorator': 'FormItem',
@@ -178,7 +211,7 @@ const Save = () => {
         },
         'x-reactions': [
           {
-            dependencies: ['....shareCluster'],
+            dependencies: ['shareCluster'],
             fulfill: {
               state: {
                 visible: '{{!$deps[0]}}',
@@ -200,7 +233,14 @@ const Save = () => {
         },
         required: true,
         'x-reactions': {
-          //后台获取数据
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible:
+                '{{["COAP_SERVER","MQTT_SERVER","WEB_SOCKET_SERVER","TCP_SERVER","UDP"].includes($deps[0])}}',
+            },
+          },
         },
         'x-validator': ['ipv4'],
       },
@@ -216,6 +256,16 @@ const Save = () => {
         type: 'number',
         'x-decorator': 'FormItem',
         'x-component': 'Select',
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible:
+                '{{["COAP_SERVER","MQTT_SERVER","WEB_SOCKET_SERVER","TCP_SERVER","UDP"].includes($deps[0])}}',
+            },
+          },
+        },
         'x-validator': [
           {
             max: 65535,
@@ -239,6 +289,16 @@ const Save = () => {
         'x-decorator': 'FormItem',
         'x-component': 'Input',
         'x-validator': ['ipv4'],
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible:
+                '{{["COAP_SERVER","MQTT_SERVER","WEB_SOCKET_SERVER","TCP_SERVER","UDP"].includes($deps[0])}}',
+            },
+          },
+        },
       },
       publicPort: {
         title: '公网端口',
@@ -251,6 +311,16 @@ const Save = () => {
         required: true,
         'x-decorator': 'FormItem',
         'x-component': 'NumberPicker',
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible:
+                '{{["COAP_SERVER","MQTT_SERVER","WEB_SOCKET_SERVER","TCP_SERVER","UDP"].includes($deps[0])}}',
+            },
+          },
+        },
         'x-validator': [
           {
             max: 65535,
@@ -262,6 +332,75 @@ const Save = () => {
           },
         ],
       },
+      mqttClient: {
+        type: 'void',
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible: '{{["MQTT_Client"].includes($deps[0])}}',
+            },
+          },
+        },
+        properties: {
+          remoteHost: {
+            title: '远程地址',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          remotePort: {
+            title: '远程端口',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          clientId: {
+            title: 'clientId',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          username: {
+            title: '用户名',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          password: {
+            title: '密码',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          maxMessageSize: {
+            title: '最大消息长度',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+          topicPrefix: {
+            title: '订阅前缀',
+            'x-decorator': 'FormItem',
+            'x-component': 'Input',
+          },
+        },
+      },
+      maxMessageSize: {
+        title: '最大消息长度',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-decorator-props': {
+          gridSpan: 1,
+          labelAlign: 'left',
+          tooltip: '对外提供访问的地址,内网环境是填写服务器的内网IP地址',
+          layout: 'vertical',
+        },
+        'x-reactions': {
+          dependencies: ['type'],
+          fulfill: {
+            state: {
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible: '{{["MQTT_SERVER","MQTT-Client"].includes($deps[0])}}',
+            },
+          },
+        },
+      },
       parserType: {
         // TCP
         required: true,
@@ -282,10 +421,11 @@ const Save = () => {
           { value: 'fixed_length', label: '固定长度' },
         ],
         'x-reactions': {
-          dependencies: ['....type'],
+          dependencies: ['type'],
           fulfill: {
             state: {
-              visible: '{{$deps[0]==="UDP"}}',
+              // visible: '{{$deps[0]==="UDP"}}',
+              visible: '{{["TCP_SERVER"].includes($deps[0])}}',
             },
           },
         },
@@ -355,22 +495,24 @@ const Save = () => {
             },
           },
           configuration: {
-            type: 'void',
-            'x-visible': false,
+            type: 'object',
+            // 'x-visible': false,
             'x-decorator': 'FormItem',
             'x-component': 'FormCollapse',
             'x-component-props': {
               formCollapse: '{{formCollapse}}',
               className: styles.configuration,
             },
-            'x-reactions': {
-              dependencies: ['.shareCluster'],
-              fulfill: {
-                state: {
-                  visible: '{{$deps[0]===true}}',
+            'x-reactions': [
+              {
+                dependencies: ['.shareCluster', 'type'],
+                fulfill: {
+                  state: {
+                    visible: '{{!!$deps[1]&&$deps[0]===true}}',
+                  },
                 },
               },
-            },
+            ],
             'x-decorator-props': {
               gridSpan: 3,
             },
@@ -391,26 +533,19 @@ const Save = () => {
               gridSpan: 3,
             },
             'x-reactions': {
-              dependencies: ['.shareCluster'],
+              dependencies: ['.shareCluster', 'type'],
               fulfill: {
                 state: {
-                  visible: '{{$deps[0]===false}}',
+                  visible: '{{!!$deps[1]&&$deps[0]===false}}',
                 },
               },
             },
             'x-visible': false,
             properties: {
-              config: {
+              cluster: {
                 type: 'array',
                 'x-component': 'ArrayCollapse',
                 'x-decorator': 'FormItem',
-                // maxItems: 2,
-                'x-validator': [
-                  {
-                    maxItems: 2,
-                    message: '集群节点已全部配置,请勿重复添加',
-                  },
-                ],
                 items: {
                   type: 'void',
                   'x-component': 'ArrayCollapse.CollapsePanel',
@@ -538,6 +673,20 @@ const Save = () => {
       },
     },
   };
+
+  const handleSave = async (data: any) => {
+    if (data.shareCluster === false) {
+      data.cluster = data.cluster?.map((item: any) => ({
+        serverId: item.serverId,
+        configuration: item,
+      }));
+    }
+    const response: any = data.id ? await service.update(data) : await service.save(data);
+    if (response.status === 200) {
+      message.success('保存成功');
+      history.back();
+    }
+  };
   return (
     <PageContainer onBack={() => history.back()}>
       <Card>
@@ -546,10 +695,19 @@ const Save = () => {
             schema={schema}
             scope={{ formCollapse, useAsyncDataSource, getSupports, getResourcesClusters }}
           />
+          <FormButtonGroup.Sticky>
+            <FormButtonGroup.FormItem>
+              {!form.readPretty ? (
+                <Submit onSubmit={handleSave}>保存</Submit>
+              ) : (
+                <Button onClick={() => (form.readPretty = false)}>编辑</Button>
+              )}
+            </FormButtonGroup.FormItem>
+          </FormButtonGroup.Sticky>
         </Form>
       </Card>
     </PageContainer>
   );
-};
+});
 
 export default Save;

+ 113 - 44
src/pages/link/Type/index.tsx

@@ -1,8 +1,14 @@
 import { useRef, useState } from 'react';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Button, Tooltip } from 'antd';
-import { BugOutlined, EditOutlined, MinusOutlined, PlusOutlined } from '@ant-design/icons';
+import { Badge, Button, message, Popconfirm, Tooltip } from 'antd';
+import {
+  CloseCircleOutlined,
+  DeleteOutlined,
+  EyeOutlined,
+  PlayCircleOutlined,
+  PlusOutlined,
+} from '@ant-design/icons';
 import { PageContainer } from '@ant-design/pro-layout';
 import type { NetworkItem } from '@/pages/link/Type/typings';
 import { useIntl } from '@@/plugin-locale/localeExports';
@@ -10,20 +16,25 @@ import SearchComponent from '@/components/SearchComponent';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import { history } from 'umi';
 import Service from '@/pages/link/service';
+import { Store } from 'jetlinks-store';
 
 export const service = new Service('network/config');
 
+/**
+ * 跳转详情页
+ * @param id
+ */
+const pageJump = (id?: string) => {
+  // 跳转详情
+  history.push(`${getMenuPathByParams(MENUS_CODE['link/Type/Save'], id)}`);
+};
+
 const Network = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
 
   const columns: ProColumns<NetworkItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       dataIndex: 'name',
       title: intl.formatMessage({
         id: 'pages.table.name',
@@ -38,19 +49,49 @@ const Network = () => {
       }),
     },
     {
+      dataIndex: 'shareCluster',
+      title: '集群',
+      renderText: (text) => (text ? '共享配置' : '独立配置'),
+    },
+    {
+      dataIndex: 'configuration',
+      title: '详情',
+      renderText: (text, record) => {
+        if (record.shareCluster) {
+          const publicHost = record.configuration.publicHost;
+          const publicPort = record.configuration.publicPort;
+          return (
+            <>
+              公网: {publicHost}:{publicPort}
+            </>
+          );
+        } else {
+          const publicHost = record.cluster?.[0]?.configuration?.publicHost;
+          const publicPort = record.cluster?.[0]?.configuration?.publicPort;
+          return (
+            <>
+              公网: {publicHost}:{publicPort}
+            </>
+          );
+        }
+      },
+    },
+    {
       dataIndex: 'state',
       title: intl.formatMessage({
         id: 'pages.searchTable.titleStatus',
         defaultMessage: '状态',
       }),
-      render: (text, record) => record.state.value,
+      render: (text, record) => {
+        if (record.state.value === 'disabled') {
+          return <Badge color="lime" text="正常" />;
+        }
+        return <Badge color="red" text="禁用" />;
+      },
     },
     {
-      dataIndex: 'provider',
-      title: intl.formatMessage({
-        id: 'pages.table.provider',
-        defaultMessage: '服务商',
-      }),
+      dataIndex: 'description',
+      title: '说明',
     },
     {
       title: intl.formatMessage({
@@ -61,35 +102,72 @@ const Network = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a onClick={() => console.log(record)}>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
+        <a
+          key="edit"
+          onClick={() => {
+            Store.set('current-network-data', record);
+            pageJump(record.id);
+          }}
+        >
+          <Tooltip title="查看">
+            <EyeOutlined />
           </Tooltip>
         </a>,
-        <a>
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.remove',
-              defaultMessage: '删除',
-            })}
+        <a key="delete">
+          <Popconfirm
+            title="确认删除?"
+            onConfirm={async () => {
+              const response: any = await service.remove(record.id);
+              if (response.status === 200) {
+                message.success('删除成功');
+                actionRef.current?.reload();
+              }
+            }}
           >
-            <MinusOutlined />
-          </Tooltip>
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove',
+                defaultMessage: '删除',
+              })}
+            >
+              <DeleteOutlined />
+            </Tooltip>
+          </Popconfirm>
         </a>,
-        <a>
-          <Tooltip
+        <a key="changeState">
+          <Popconfirm
             title={intl.formatMessage({
-              id: 'pages.notice.option.debug',
-              defaultMessage: '调试',
+              id: `pages.data.option.${record.state.value}.tips`,
+              defaultMessage: `确认${record.state.value === 'enabled' ? '禁用' : '启用'}?`,
             })}
+            onConfirm={async () => {
+              // await service.update({
+              //   id: record.id,
+              //   status: record.status ? 0 : 1,
+              // });
+              const map = {
+                disabled: 'start',
+                enabled: 'shutdown',
+              };
+              await service.changeState(record.id, map[record.state.value]);
+              message.success(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
+            }}
           >
-            <BugOutlined />
-          </Tooltip>
+            <Tooltip
+              title={intl.formatMessage({
+                id: `pages.data.option.${record.state.value}`,
+                defaultMessage: record.state.value === 'enabled' ? '禁用' : '启用',
+              })}
+            >
+              {record.state.value === 'disabled' ? <CloseCircleOutlined /> : <PlayCircleOutlined />}
+            </Tooltip>
+          </Popconfirm>
         </a>,
       ],
     },
@@ -97,15 +175,6 @@ const Network = () => {
 
   const [param, setParam] = useState({});
 
-  /**
-   * 跳转详情页
-   * @param id
-   */
-  const pageJump = (id?: string) => {
-    // 跳转详情
-    history.push(`${getMenuPathByParams(MENUS_CODE['link/Type/Save'], id)}`);
-  };
-
   return (
     <PageContainer>
       <SearchComponent

+ 2 - 1
src/pages/link/Type/typings.d.ts

@@ -1,4 +1,4 @@
-import type { BaseItem } from '@/utils/typings';
+import type { BaseItem, State } from '@/utils/typings';
 
 type NetworkItem = {
   shareCluster: boolean;
@@ -12,4 +12,5 @@ type NetworkItem = {
   createTime: number;
   creatorId: string;
   configuration: Record<string, any>;
+  cluster: any[];
 } & BaseItem;

+ 3 - 0
src/pages/link/service.ts

@@ -33,6 +33,9 @@ class Service extends BaseService<NetworkItem> {
     request(`${SystemConst.API_BASE}/network/resources/alive/_all`, {
       method: 'GET',
     });
+
+  changeState = (id: string, status: 'start' | 'shutdown') =>
+    request(`${SystemConst.API_BASE}/network/config/${id}/_${status}`, { method: 'POST' });
 }
 
 export default Service;