xieyonghong 3 лет назад
Родитель
Сommit
90ab6ae355

BIN
public/images/device/device-number.png


+ 4 - 1
src/pages/device/Instance/Detail/Diagnose/index.tsx

@@ -47,8 +47,11 @@ const Diagnose = observer(() => {
     return () => {
       DiagnoseStatusModel.list = [];
       DiagnoseStatusModel.count = 0;
+      DiagnoseStatusModel.percent = 0;
+      DiagnoseStatusModel.status = 'loading';
+      DiagnoseStatusModel.state = 'loading';
     };
-  }, []);
+  }, [InstanceModel.active]);
 
   const activeStyle = {
     background: '#FFFFFF',

+ 3 - 0
src/pages/device/Instance/Detail/Info/index.tsx

@@ -65,6 +65,9 @@ const Info = observer(() => {
               </div>
             </Tooltip>
           </Descriptions.Item>
+          <Descriptions.Item label={'产品分类'}>
+            {InstanceModel.detail?.classifiedName}
+          </Descriptions.Item>
           <Descriptions.Item
             label={intl.formatMessage({
               id: 'pages.device.instanceDetail.deviceType',

+ 21 - 9
src/pages/device/Instance/Detail/index.tsx

@@ -1,7 +1,7 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { InstanceModel } from '@/pages/device/Instance';
 import { history, useParams } from 'umi';
-import { Badge, Card, Descriptions, Divider, Space, Tooltip } from 'antd';
+import { Badge, Card, Descriptions, Divider, message, Space, Tooltip } from 'antd';
 import type { ReactNode } from 'react';
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
@@ -24,7 +24,7 @@ import SystemConst from '@/utils/const';
 import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
-import { ExclamationCircleOutlined, QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
+import { QuestionCircleOutlined, SyncOutlined } from '@ant-design/icons';
 import Service from '@/pages/device/Instance/service';
 import useLocation from '@/hooks/route/useLocation';
 import { onlyMessage } from '@/utils/util';
@@ -250,6 +250,7 @@ const InstanceDetail = observer(() => {
   }, []);
 
   useEffect(() => {
+    // console.log(InstanceModel.current)
     if (!InstanceModel.current && !params.id) {
       history.goBack();
     } else {
@@ -308,6 +309,7 @@ const InstanceDetail = observer(() => {
               size={'small'}
               tooltip={{
                 title: InstanceModel.detail?.productName,
+                placement: 'topLeft',
               }}
               isPermission={!!getMenuPathByCode(MENUS_CODE['device/Product'])}
               onClick={() => {
@@ -391,12 +393,19 @@ const InstanceDetail = observer(() => {
                 断开连接
               </PermissionButton>
             )}
-            {InstanceModel.detail?.accessProvider === 'child-device' ? (
-              <div style={{ fontSize: 14, marginLeft: 10, fontWeight: 400 }}>
-                <ExclamationCircleOutlined style={{ fontSize: 14, marginRight: 5 }} />
-                {InstanceModel.detail?.features?.find((item) => item.id === 'selfManageState')
-                  ? '该设备的在线状态与父设备(网关设备)保持一致'
-                  : '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'}
+            {InstanceModel.detail?.accessProvider === 'child-device' &&
+            InstanceModel.detail?.state?.value === 'offline' ? (
+              <div>
+                <Tooltip
+                  placement="bottom"
+                  title={
+                    InstanceModel.detail?.features?.find((item) => item.id === 'selfManageState')
+                      ? '该设备的在线状态与父设备(网关设备)保持一致'
+                      : '该设备在线状态由设备自身运行状态决定,不继承父设备(网关设备)的在线状态'
+                  }
+                >
+                  <QuestionCircleOutlined style={{ fontSize: 14, marginRight: 5 }} />
+                </Tooltip>
               </div>
             ) : (
               ''
@@ -421,7 +430,10 @@ const InstanceDetail = observer(() => {
           onClick={() => {
             getDetail(params.id);
             service.getConfigMetadata(params.id).then((config) => {
-              InstanceModel.config = config?.result || [];
+              if (config.status === 200) {
+                InstanceModel.config = config?.result || [];
+                message.success('操作成功!');
+              }
             });
           }}
           style={{ fontSize: 20, marginRight: 20 }}

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -44,6 +44,7 @@ export type DeviceInstance = {
   accessId?: string;
   features?: any[];
   parentId?: string;
+  classifiedName?: string;
 };
 
 type Unit = {

+ 32 - 15
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -12,6 +12,8 @@ import { getMenuPathByCode } from '@/utils/menu';
 import PermissionButton from '@/components/PermissionButton';
 import { onlyMessage } from '@/utils/util';
 import Empty from '@/components/Empty';
+import _ from 'lodash';
+import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
 
 interface Props {
   close: () => void;
@@ -128,6 +130,15 @@ const AccessConfig = (props: Props) => {
     handleSearch(param);
   }, []);
 
+  const modifyArray = (oldData: any[], newData: any[]) => {
+    newData.map((item) => {
+      if (!_.map(oldData, 'id').includes(item.id)) {
+        oldData.push(item);
+      }
+    });
+    return oldData;
+  };
+
   return (
     <Modal
       onCancel={() => {
@@ -138,7 +149,7 @@ const AccessConfig = (props: Props) => {
       title={'设备接入配置'}
       onOk={async () => {
         if (!!currrent) {
-          const resp: any = await service1.update({
+          const obj: any = {
             ...productModel.current,
             transportProtocol: currrent.transport,
             protocolName: currrent.protocolDetail.name,
@@ -146,7 +157,26 @@ const AccessConfig = (props: Props) => {
             accessName: currrent.name,
             accessProvider: currrent.provider,
             messageProtocol: currrent.protocol,
-          });
+          };
+          const metatdata = JSON.parse(productModel.current?.metadata || '{}');
+          if (obj.accessProvider === 'gb28181-2016') {
+            const response = await service.getConfigView(
+              obj?.messageProtocol || '',
+              obj?.transportProtocol || '',
+            );
+            if (response.status === 200) {
+              const ndata = JSON.parse(response.result?.metadata || '{}');
+              const mdata = {
+                events: modifyArray(metatdata?.events || [], ndata?.events || []),
+                properties: modifyArray(metatdata?.properties || [], ndata?.properties || []),
+                functions: modifyArray(metatdata?.functions || [], ndata?.functions || []),
+                tags: modifyArray(metatdata?.tags || [], ndata?.tags || []),
+              };
+              MetadataAction.insert(mdata);
+              obj.metadata = JSON.stringify(mdata);
+            }
+          }
+          const resp: any = await service1.update(obj);
           if (resp.status === 200) {
             service1.detail(productModel.current?.id || '').then((res) => {
               if (res.status === 200) {
@@ -155,19 +185,6 @@ const AccessConfig = (props: Props) => {
               }
               close();
             });
-            // service1
-            //   .changeDeploy(productModel.current?.id || '', 'deploy')
-            //   .subscribe((response) => {
-            //     if (response) {
-            //       service1.detail(productModel.current?.id || '').then((res) => {
-            //         if (res.status === 200) {
-            //           productModel.current = { ...res.result };
-            //           message.success('操作成功!');
-            //         }
-            //         close();
-            //       });
-            //     }
-            //   });
           }
         } else {
           onlyMessage('请选择接入方式', 'error');

+ 162 - 109
src/pages/device/Product/Detail/Access/index.tsx

@@ -2,7 +2,7 @@ import { Badge, Button, Col, Empty, Row, Table, Tooltip } from 'antd';
 import { service } from '@/pages/link/AccessConfig';
 import { productModel, service as productService } from '@/pages/device/Product';
 import styles from './index.less';
-import type { SetStateAction } from 'react';
+import { SetStateAction, useRef } from 'react';
 import { useEffect, useState } from 'react';
 import AccessConfig from './AccessConfig';
 import ReactMarkdown from 'react-markdown';
@@ -40,6 +40,7 @@ const Access = () => {
   const [access, setAccess] = useState<any>();
   const { permission } = usePermissions('device/Product');
   const [dataSource, setDataSource] = useState<any[]>([]);
+  const [storageList, setStorageList] = useState<any[]>([]);
 
   const MetworkTypeMapping = new Map();
   MetworkTypeMapping.set('websocket-server', 'WEB_SOCKET_SERVER');
@@ -55,6 +56,7 @@ const Access = () => {
   const [configVisible, setConfigVisible] = useState<boolean>(false);
 
   const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
+  const ref = useRef(0);
 
   const steps = [
     {
@@ -87,7 +89,7 @@ const Access = () => {
   ];
   const steps1 = [
     {
-      element: '#driver-config',
+      element: '.config',
       popover: {
         className: 'driver',
         title: `<div id='title'>填写配置</div><div id='guide'>1/4</div>`,
@@ -272,6 +274,10 @@ const Access = () => {
 
   const id = productModel.current?.id;
 
+  const guide = (data: any) => {
+    service.productGuideSave(data);
+  };
+
   useEffect(() => {
     const driver = new Driver({
       allowClose: false,
@@ -279,35 +285,63 @@ const Access = () => {
       closeBtnText: '不在提示',
       nextBtnText: '下一步',
       prevBtnText: '上一步',
-      // onDeselected:(e)=>{
-      //   console.log(e)
-      // },
       onNext: () => {
-        console.log('下一步');
+        ref.current = ref.current + 1;
+      },
+      onPrevious: () => {
+        ref.current = ref.current - 1;
+      },
+      onReset: () => {
+        if (ref.current !== 3) {
+          guide({
+            name: 'guide',
+            content: 'skip',
+          });
+        }
+        ref.current = 0;
+      },
+    });
+    const driver1 = new Driver({
+      allowClose: false,
+      doneBtnText: '我知道了',
+      closeBtnText: '不在提示',
+      nextBtnText: '下一步',
+      prevBtnText: '上一步',
+      onNext: () => {
+        ref.current = ref.current + 1;
       },
       onPrevious: () => {
-        console.log('上一步');
+        ref.current = ref.current - 1;
       },
       onReset: () => {
-        console.log('关闭');
+        if (ref.current !== 4) {
+          guide({
+            name: 'guide',
+            content: 'skip',
+          });
+        }
+        ref.current = 0;
       },
-      // onDeselected:()=>{
-      //   console.log('oncolse')
-      // }
     });
     setVisible(!!productModel.current?.accessId);
     if (productModel.current?.accessId) {
       if (id) {
         productService
           .getConfigMetadata(id)
-          .then((resp: { result: SetStateAction<ConfigMetadata[]> }) => {
+          .then(async (resp: { result: SetStateAction<ConfigMetadata[]> }) => {
             setMetadata(resp.result);
-            if (resp.result && resp.result.length > 0) {
-              driver.defineSteps(steps1);
-              driver.start();
+            //判断引导页是否跳过
+            const res = await service.productGuide();
+            if (res.result && res.result?.content === 'skip') {
+              return;
             } else {
-              driver.defineSteps(steps);
-              driver.start();
+              if (resp.result && resp.result.length > 0) {
+                driver1.defineSteps(steps1);
+                driver1.start();
+              } else {
+                driver.defineSteps(steps);
+                driver.start();
+              }
             }
           });
       }
@@ -330,29 +364,24 @@ const Access = () => {
           });
       }
     }
+    productService.getStoragList().then((resp) => {
+      if (resp.status === 200) {
+        setStorageList(
+          resp.result.map((i: any) => ({
+            label: i.name,
+            value: i.id,
+          })),
+        );
+      }
+    });
   }, [productModel.current]);
 
-  // useEffect(() => {
-  //   console.log(productModel.current)
-  //   console.log(productModel.current?.accessId)
-  //   const driver = new Driver({
-  //     allowClose: false,
-  //     doneBtnText: '我知道了',
-  //     closeBtnText: '不在提示',
-  //     nextBtnText: '下一步',
-  //     prevBtnText: '上一步'
-  //   });
-  //   if (productModel.current?.accessId) {
-  //     setTimeout(() => {
-  //       driver.defineSteps(steps)
-  //       driver.start();
-  //     }, 1000)
-  //   }
-  // }, [])
-
   const form = createForm({
     validateFirst: true,
-    initialValues: productModel.current?.configuration,
+    initialValues: {
+      ...productModel.current?.configuration,
+      storePolicy: productModel.current?.storePolicy,
+    },
   });
 
   const SchemaField = createSchemaField({
@@ -367,13 +396,16 @@ const Access = () => {
   });
 
   const configToSchema = (data: ConfigProperty[]) => {
-    const obj = {};
+    const obj: any = {};
     data.forEach((item) => {
       obj[item?.property] = {
         type: 'string',
         title: item.name,
         'x-decorator': 'FormItem',
         'x-component': componentMap[item.type.type],
+        'x-component-props': {
+          placeholder: item.type.type === 'enum' ? '请选择' : '请输入',
+        },
         'x-decorator-props': {
           tooltip: item.description,
           gridSpan: 1,
@@ -391,6 +423,7 @@ const Access = () => {
             : [],
       };
     });
+
     return obj;
   };
 
@@ -406,84 +439,101 @@ const Access = () => {
   };
 
   const renderConfigCard = () => {
-    return (
-      metadata &&
-      metadata.length > 0 &&
-      metadata?.map((item: any) => {
-        const itemSchema: ISchema = {
-          type: 'object',
-          properties: {
-            grid: {
-              type: 'void',
-              'x-component': 'FormGrid',
-              'x-component-props': {
-                maxColumns: 1,
-                minColumns: 1,
-                columnGap: 48,
-              },
-              title: (
-                <TitleComponent
-                  data={
-                    <div className="config">
-                      {item.name}
-                      <Tooltip title="此配置来自于该产品接入方式所选择的协议">
-                        <QuestionCircleOutlined />
-                      </Tooltip>
-                    </div>
-                  }
-                />
-              ),
-              'x-decorator': 'FormItem',
-              'x-decorator-props': {
-                gridSpan: 1,
-                labelAlign: 'left',
-                layout: 'vertical',
-              },
-              properties: configToSchema(item.properties),
+    const itemSchema: any = (metadata || []).map((item: any) => {
+      return {
+        type: 'object',
+        properties: {
+          grid: {
+            type: 'void',
+            'x-component': 'FormGrid',
+            'x-component-props': {
+              maxColumns: 1,
+              minColumns: 1,
+              columnGap: 48,
+            },
+            title: (
+              <TitleComponent
+                data={
+                  <div className="config">
+                    {item.name}
+                    <Tooltip title="此配置来自于该产品接入方式所选择的协议">
+                      <QuestionCircleOutlined />
+                    </Tooltip>
+                  </div>
+                }
+              />
+            ),
+            'x-decorator': 'FormItem',
+            'x-decorator-props': {
+              gridSpan: 1,
+              labelAlign: 'left',
+              layout: 'vertical',
             },
+            properties: configToSchema(item.properties),
           },
-        };
-
-        return (
-          <PreviewText.Placeholder value="-" key={'config'}>
-            <Form form={form} layout="vertical">
-              <FormLayout>
-                <SchemaField schema={itemSchema} />
-              </FormLayout>
-              <Button
-                type="primary"
-                onClick={async () => {
-                  const values = (await form.submit()) as any;
-                  const resp = await productService.modify(id || '', {
-                    id,
-                    configuration: { ...values },
-                  });
-                  if (resp.status === 200) {
-                    onlyMessage('操作成功!');
-                    if ((window as any).onTabSaveSuccess) {
-                      if (resp.result) {
-                        (window as any).onTabSaveSuccess(resp);
-                        setTimeout(() => window.close(), 300);
-                      }
-                    } else {
-                      getDetailInfo();
-                    }
+        },
+      };
+    });
+    const schema: ISchema = {
+      type: 'object',
+      properties: {
+        ...itemSchema,
+        storePolicy: {
+          type: 'string',
+          title: <TitleComponent data={'存储策略'} />,
+          'x-decorator': 'FormItem',
+          'x-component': 'Select',
+          'x-component-props': {
+            placeholder: '请选择存储策略',
+          },
+          // required: true,
+          default: 'default-column',
+          'x-decorator-props': {
+            // tooltip: '使用指定的存储策略来存储设备数据',
+            gridSpan: 1,
+            labelAlign: 'left',
+            layout: 'vertical',
+          },
+          enum: storageList,
+        },
+      },
+    };
+    return (
+      <PreviewText.Placeholder value="-" key={'config'}>
+        <Form form={form} layout="vertical">
+          <FormLayout>
+            <SchemaField schema={schema} />
+          </FormLayout>
+          <Button
+            type="primary"
+            onClick={async () => {
+              const values = (await form.submit()) as any;
+              const { storePolicy, ...extra } = values;
+              const resp = await productService.modify(id || '', {
+                id,
+                configuration: { ...extra },
+                storePolicy: storePolicy,
+              });
+              if (resp.status === 200) {
+                onlyMessage('操作成功!');
+                if ((window as any).onTabSaveSuccess) {
+                  if (resp.result) {
+                    (window as any).onTabSaveSuccess(resp);
+                    setTimeout(() => window.close(), 300);
                   }
-                }}
-              >
-                保存
-              </Button>
-            </Form>
-          </PreviewText.Placeholder>
-        );
-      })
+                } else {
+                  getDetailInfo();
+                }
+              }
+            }}
+          >
+            保存
+          </Button>
+        </Form>
+      </PreviewText.Placeholder>
     );
   };
 
-  // useEffect(() => {
-
-  // }, [])
-
   return (
     <div>
       {!visible ? (
@@ -540,6 +590,9 @@ const Access = () => {
                           更换
                         </Button>
                       </Tooltip>
+                      {/* <Button onClick={async()=>{
+                          await service.productGuideDetail()
+                        }}>删除</Button> */}
                     </span>
                   }
                 />

+ 5 - 0
src/pages/device/Product/service.ts

@@ -164,6 +164,11 @@ class Service extends BaseService<ProductItem> {
     request(`/${SystemConst.API_BASE}/protocol/${id}/detail`, {
       method: 'POST',
     });
+  // 存储策略
+  public getStoragList = () =>
+    request(`/${SystemConst.API_BASE}/device/product/storage/policies`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 1 - 0
src/pages/device/Product/typings.d.ts

@@ -27,6 +27,7 @@ export type ProductItem = {
   accessId?: string;
   accessName?: string;
   photoUrl?: string;
+  storePolicy?: string;
   accessProvider?: string;
 };
 

+ 20 - 2
src/pages/device/components/Metadata/Import/index.tsx

@@ -12,6 +12,9 @@ import { Store } from 'jetlinks-store';
 import SystemConst from '@/utils/const';
 import { onlyMessage } from '@/utils/util';
 import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { InstanceModel } from '@/pages/device/Instance';
+import _ from 'lodash';
+import { DeviceMetadata } from '@/pages/device/Product/typings';
 
 interface Props {
   visible: boolean;
@@ -181,6 +184,19 @@ const Import = (props: Props) => {
     },
   };
 
+  const operateLimits = (mdata: DeviceMetadata) => {
+    const obj: DeviceMetadata = { ...mdata };
+    const old = JSON.parse(InstanceModel.detail?.metadata || '{}');
+    const fid = _.map(InstanceModel.detail?.features || [], 'id');
+    if (fid.includes('eventNotModifiable')) {
+      obj.events = old?.event || [];
+    }
+    if (fid.includes('propertyNotModifiable')) {
+      obj.properties = old?.properties || {};
+    }
+    return obj;
+  };
+
   const handleImport = async () => {
     const data = (await form.submit()) as any;
 
@@ -188,14 +204,16 @@ const Import = (props: Props) => {
       service.convertMetadata('from', 'alink', data.import).subscribe({
         next: async (meta) => {
           onlyMessage('导入成功');
-          await service.modify(param.id, { metadata: JSON.stringify(meta) });
+          await service.modify(param.id, { metadata: JSON.stringify(operateLimits(meta)) });
         },
         error: () => {
           onlyMessage('发生错误!', 'error');
         },
       });
     } else {
-      const resp = await service.modify(param.id, { metadata: data[data.type] });
+      const resp = await service.modify(param.id, {
+        metadata: JSON.stringify(operateLimits(JSON.parse(data[data?.type] || '{}'))),
+      });
       if (resp.status === '200') {
         onlyMessage('导入成功');
       }

+ 80 - 39
src/pages/home/device/index.tsx

@@ -1,6 +1,6 @@
 import { Col, message, Row, Tooltip } from 'antd';
 import { PermissionButton } from '@/components';
-import { Body } from '../components';
+import { Body, Guide } from '../components';
 import Statistics from '../components/Statistics';
 import Steps from '../components/Steps';
 import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
@@ -14,7 +14,7 @@ import DeviceChoose from '../components/DeviceChoose';
 const Device = () => {
   const productPermission = PermissionButton.usePermission('device/Product').permission;
   const devicePermission = PermissionButton.usePermission('device/Instance').permission;
-  // const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
+  const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
 
   const [productCount, setProductCount] = useState<number>(0);
   const [deviceCount, setDeviceCount] = useState<number>(0);
@@ -44,47 +44,88 @@ const Device = () => {
   const history = useHistory();
   // // 跳转
 
-  // const guideList = [
-  //   {
-  //     key: 'product',
-  //     name: '创建产品',
-  //     english: 'STEP1',
-  //     auth: !!productPermission.add,
-  //     url: 'device/Product',
-  //     param: {
-  //       save: true,
-  //     },
-  //   },
-  //   {
-  //     key: 'device',
-  //     name: '创建设备',
-  //     english: 'STEP2',
-  //     auth: !!devicePermission.add,
-  //     url: 'device/Instance',
-  //     param: {
-  //       save: true,
-  //     },
-  //   },
-  //   {
-  //     key: 'rule-engine',
-  //     name: '规则引擎',
-  //     english: 'STEP3',
-  //     auth: !!rulePermission.add,
-  //     url: 'rule-engine/Instance',
-  //     param: {
-  //       save: true,
-  //     },
-  //   },
-  // ];
+  const guideList = [
+    {
+      key: 'product',
+      name: '创建产品',
+      english: 'STEP1',
+      auth: !!productPermission.add,
+      url: 'device/Product',
+      param: {
+        save: true,
+      },
+    },
+    {
+      key: 'device',
+      name: '创建设备',
+      english: 'STEP2',
+      auth: !!devicePermission.add,
+      url: 'device/Instance',
+      param: {
+        save: true,
+      },
+    },
+    {
+      key: 'rule-engine',
+      name: '规则引擎',
+      english: 'STEP3',
+      auth: !!rulePermission.add,
+      url: 'rule-engine/Instance',
+      param: {
+        save: true,
+      },
+    },
+  ];
 
   return (
     <Row gutter={24}>
-      <Col span={18}>
+      <Col span={14}>
+        <Guide
+          title="物联网引导"
+          data={guideList}
+          // jump={(auth: boolean, url: string, param: string) => {
+          //   pageJump(auth, url, param);
+          // }}
+        />
+      </Col>
+      <Col span={10}>
+        <Statistics
+          data={[
+            {
+              name: '产品数量',
+              value: productCount,
+              children: require('/public/images/home/top-2.png'),
+            },
+            {
+              name: '设备数量',
+              value: deviceCount,
+              children: '',
+            },
+          ]}
+          title="设备统计"
+          extra={
+            <div style={{ fontSize: 14, fontWeight: 400 }}>
+              <a
+                onClick={() => {
+                  const url = getMenuPathByCode(MENUS_CODE['device/DashBoard']);
+                  if (!!url) {
+                    history.push(`${url}`);
+                  } else {
+                    message.warning('暂无权限,请联系管理员');
+                  }
+                }}
+              >
+                详情
+              </a>
+            </div>
+          }
+        />
+      </Col>
+      <Col span={24}>
+        <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
+      </Col>
+      <Col span={24}>
         <Steps
-          style={{
-            height: 275,
-            gridColumnGap: 20,
-          }}
           title={
             <span>
               设备接入推荐步骤

+ 70 - 79
src/pages/home/ops/index.tsx

@@ -1,5 +1,6 @@
 import { Col, message, Row, Tooltip } from 'antd';
-// import Guide from '../components/Guide';
+import Guide from '../components/Guide';
+import { PermissionButton } from '@/components';
 import Statistics from '../components/Statistics';
 import Pie from '@/pages/home/components/Pie';
 import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
@@ -15,9 +16,9 @@ const Ops = () => {
   const [subscribeTopic] = useSendWebsocketMessage();
   const history = useHistory();
 
-  // const accessPermission = getMenuPathByCode(MENUS_CODE['link/AccessConfig']);
-  // const logPermission = getMenuPathByCode(MENUS_CODE['Log']);
-  // const linkPermission = getMenuPathByCode(MENUS_CODE['link/DashBoard']);
+  const productPermission = PermissionButton.usePermission('device/Product').permission;
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
+  const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
 
   const [cpuValue, setCpuValue] = useState<number>(0);
   const [jvmValue, setJvmValue] = useState<number>(0);
@@ -56,38 +57,73 @@ const Ops = () => {
       jvmRealTime?.unsubscribe();
     };
   }, []);
-
-  // const guideOpsList = [
-  //   {
-  //     key: 'access',
-  //     name: '设备接入配置',
-  //     english: 'DEVICE ACCESS CONFIGURATION',
-  //     auth: !!accessPermission,
-  //     url: accessPermission,
-  //   },
-  //   {
-  //     key: 'logger',
-  //     name: '日志排查',
-  //     english: 'LOG SCREEN',
-  //     auth: !!logPermission,
-  //     url: logPermission,
-  //     param: {
-  //       key: 'system',
-  //     },
-  //   },
-  //   {
-  //     key: 'realtime',
-  //     name: '实时监控',
-  //     english: 'REAL-TIME MONITORING',
-  //     auth: !!linkPermission,
-  //     url: linkPermission,
-  //     param: {
-  //       save: true,
-  //     },
-  //   },
-  // ];
+  const guideOpsList: any[] = [
+    {
+      key: 'product',
+      name: '设备接入配置',
+      english: 'STEP1',
+      auth: !!productPermission.add,
+      url: 'device/Product',
+      param: '?save=true',
+    },
+    {
+      key: 'device',
+      name: '日志排查',
+      english: 'STEP2',
+      auth: !!devicePermission.add,
+      url: 'device/Instance',
+      param: '?save=true',
+    },
+    {
+      key: 'rule-engine',
+      name: '实时监控',
+      english: 'STEP3',
+      auth: !!rulePermission.add,
+      url: 'rule-engine/Instance',
+      param: '?save=true',
+    },
+  ];
   return (
     <Row gutter={24}>
+      <Col span={14}>
+        <Guide title="运维引导" data={guideOpsList} />
+      </Col>
+      <Col span={10}>
+        <Statistics
+          data={[
+            {
+              name: 'CPU使用率',
+              value: String(cpuValue) + '%',
+              children: <Pie value={cpuValue} />,
+            },
+            {
+              name: 'JVM内存',
+              value: String(jvmValue) + '%',
+              children: <Pie value={jvmValue} />,
+            },
+          ]}
+          title="基础统计"
+          extra={
+            <div style={{ fontSize: 14, fontWeight: 400 }}>
+              <a
+                onClick={() => {
+                  const url = getMenuPathByCode(MENUS_CODE['device/DashBoard']);
+                  if (!!url) {
+                    history.push(`${url}`);
+                  } else {
+                    message.warning('暂无权限,请联系管理员');
+                  }
+                }}
+              >
+                详情
+              </a>
+            </div>
+          }
+        />
+      </Col>
+      <Col span={24}>
+        <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
+      </Col>
       <Col span={24}>
         <Steps
           title={
@@ -169,51 +205,6 @@ const Ops = () => {
           ]}
         />
       </Col>
-      <Col span={18} style={{ marginTop: 24 }}>
-        <Body
-          title={'平台架构图'}
-          english={'PLATFORM ARCHITECTURE DIAGRAM'}
-          url={require('/public/images/home/content1.png')}
-        />
-      </Col>
-      <Col span={6} style={{ marginTop: 24 }}>
-        <Statistics
-          style={{ gridTemplateColumns: 'repeat(1, 1fr)' }}
-          height={448}
-          data={[
-            {
-              name: 'CPU使用率',
-              value: String(cpuValue) + '%',
-              children: <Pie value={cpuValue} />,
-            },
-            {
-              name: 'JVM内存',
-              value: String(jvmValue) + '%',
-              children: <Pie value={jvmValue} />,
-            },
-          ]}
-          title="基础统计"
-          extra={
-            <div style={{ fontSize: 14, fontWeight: 400 }}>
-              <a
-                onClick={() => {
-                  const url = getMenuPathByCode(MENUS_CODE['device/DashBoard']);
-                  if (!!url) {
-                    history.push(`${url}`);
-                  } else {
-                    message.warning('暂无权限,请联系管理员');
-                  }
-                }}
-              >
-                详情
-              </a>
-            </div>
-          }
-        />
-      </Col>
-      {/* <Col span={24}>
-        <Body title={'平台架构图'} english={'PLATFORM ARCHITECTURE DIAGRAM'} />
-      </Col> */}
     </Row>
   );
 };

+ 94 - 0
src/pages/link/AccessConfig/Detail/Media/SipSelectComponent/index.tsx

@@ -0,0 +1,94 @@
+import { Select } from 'antd';
+import { useEffect, useState } from 'react';
+
+interface SipSelectComponentProps {
+  onChange?: (data: any) => void;
+  value?: {
+    host?: string;
+    port?: number;
+  };
+  type?: boolean;
+  data: any[];
+}
+
+const SipSelectComponent = (props: SipSelectComponentProps) => {
+  const { value, onChange } = props;
+  const [data, setData] = useState<{ host?: string; port?: number } | undefined>(value);
+  const [list, setList] = useState<any[]>([]);
+
+  useEffect(() => {
+    setData(value);
+  }, [value]);
+
+  useEffect(() => {
+    if (!props.type) {
+      setData(value || { host: '0.0.0.0' });
+      if (!value) {
+        const dt: any = props.data.find((i) => i.host === '0.0.0.0');
+        setList(dt?.portList || []);
+      } else {
+        const dt: any = props.data.find((i) => i.host === value.host);
+        setList(dt?.portList || []);
+      }
+    }
+  }, [props.type]);
+
+  return (
+    <div style={{ display: 'flex', alignItems: 'center' }}>
+      <Select
+        showSearch
+        value={data?.host}
+        style={{ marginRight: 10 }}
+        placeholder="请选择IP地址"
+        disabled={!props.type}
+        onChange={(e) => {
+          if (onChange) {
+            const item = {
+              port: undefined,
+              host: e,
+            };
+            onChange(item);
+            const dt: any = props.data.find((i) => i.host === e);
+            setList(dt?.portList || []);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {(props.data || []).map((item: any) => (
+          <Select.Option key={item.host} value={item.host}>
+            {item.host}
+          </Select.Option>
+        ))}
+      </Select>
+      <Select
+        showSearch
+        style={{ minWidth: 100 }}
+        value={data?.port}
+        placeholder="请选择端口"
+        optionFilterProp="children"
+        onChange={(e: number) => {
+          if (onChange) {
+            const item = {
+              ...data,
+              port: e,
+            };
+            onChange(item);
+          }
+        }}
+        filterOption={(input: string, option: any) =>
+          String(option?.children)?.toLowerCase()?.indexOf(String(input).toLowerCase()) >= 0
+        }
+      >
+        {list.map((item) => (
+          <Select.Option key={item?.port} value={item?.port}>
+            {(item?.transports || []).join('/')}({item?.port})
+          </Select.Option>
+        ))}
+      </Select>
+    </div>
+  );
+};
+
+export default SipSelectComponent;

+ 25 - 3
src/pages/link/AccessConfig/Detail/Media/index.tsx

@@ -22,6 +22,7 @@ import TitleComponent from '@/components/TitleComponent';
 import { InfoCircleOutlined } from '@ant-design/icons';
 import { onlyMessage, testIP } from '@/utils/util';
 import { getButtonPermission } from '@/utils/menu';
+import SipSelectComponent from './SipSelectComponent';
 
 type LocationType = {
   id?: string;
@@ -38,6 +39,7 @@ const Media = (props: Props) => {
   const [form] = Form.useForm();
   const [configuration, setConfiguration] = useState<any>({});
   const [clusters, setClusters] = useState<any[]>([]);
+  const [alist, setAList] = useState<any[]>([]);
 
   const location = useLocation<LocationType>();
 
@@ -88,6 +90,16 @@ const Media = (props: Props) => {
     }
   };
 
+  useEffect(() => {
+    if (props.provider?.id === 'gb28181-2016') {
+      service.getResourcesCurrent().then((resp) => {
+        if (resp.status === 200) {
+          setAList(resp.result);
+        }
+      });
+    }
+  }, [props.provider]);
+
   const BasicRender = () => {
     const SchemaField = createSchemaField({
       components: {
@@ -96,6 +108,7 @@ const Media = (props: Props) => {
         Select,
         Radio,
         SipComponent,
+        SipSelectComponent,
         FormGrid,
         FormCollapse,
         ArrayCollapse,
@@ -146,6 +159,7 @@ const Media = (props: Props) => {
       },
       properties: {
         clusterNodeId: {
+          type: 'string',
           title: '节点名称',
           'x-component': 'Select',
           'x-decorator': 'FormItem',
@@ -159,13 +173,17 @@ const Media = (props: Props) => {
         sip: {
           title: 'SIP 地址',
           'x-decorator': 'FormItem',
-          'x-component': 'SipComponent',
+          'x-component': 'SipSelectComponent',
           'x-decorator-props': {
             gridSpan: 1,
             labelAlign: 'left',
             layout: 'vertical',
             tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
           },
+          'x-component-props': {
+            data: alist,
+            type: true,
+          },
           'x-validator': [
             {
               required: true,
@@ -185,7 +203,6 @@ const Media = (props: Props) => {
             layout: 'vertical',
           },
           required: true,
-          type: 'number',
           'x-decorator': 'FormItem',
           'x-component': 'SipComponent',
           'x-validator': [
@@ -297,9 +314,13 @@ const Media = (props: Props) => {
                   properties: {
                     sip: {
                       title: 'SIP 地址',
-                      'x-component': 'SipComponent',
+                      'x-component': 'SipSelectComponent',
                       'x-decorator': 'FormItem',
                       required: true,
+                      'x-component-props': {
+                        data: alist,
+                        type: false,
+                      },
                       'x-decorator-props': {
                         gridSpan: 1,
                         labelAlign: 'left',
@@ -405,6 +426,7 @@ const Media = (props: Props) => {
               <Button
                 onClick={async () => {
                   const value = await aform.submit<any>();
+                  console.log(value);
                   let param: any = {};
                   if (value.configuration.shareCluster) {
                     const hostPort = { ...value.configuration.hostPort };

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

@@ -51,6 +51,24 @@ class Service extends BaseService<AccessItem> {
     request(`/${SystemConst.API_BASE}/network/resources/clusters`, {
       method: 'GET',
     });
+  //引导页-不在提示
+  public productGuide = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/product/guide`, {
+      method: 'GET',
+    });
+  public productGuideSave = (data: any) =>
+    request(`/${SystemConst.API_BASE}/user/settings/product/guide`, {
+      method: 'PATCH',
+      data,
+    });
+  public productGuideDetail = () =>
+    request(`/${SystemConst.API_BASE}/user/settings/product/guide`, {
+      method: 'DELETE',
+    });
+  public getResourcesCurrent = () =>
+    request(`${SystemConst.API_BASE}/network/resources/alive/_current`, {
+      method: 'GET',
+    });
 }
 
 export default Service;

+ 22 - 22
src/pages/link/Protocol/save/index.tsx

@@ -20,30 +20,30 @@ interface Props {
 const Save = (props: Props) => {
   const [data, setData] = useState<ProtocolItem | undefined>(props.data);
   const { permission } = PermissionButton.usePermission('link/Protocol');
-  const [count, setCount] = useState<number>(0);
+  // const [count, setCount] = useState<number>(0);
 
   useEffect(() => {
     setData(props.data);
-    if (props.data?.id) {
-      service
-        .productCount({
-          terms: [
-            {
-              terms: [
-                {
-                  column: 'message_protocol',
-                  value: props.data.id,
-                },
-              ],
-            },
-          ],
-        })
-        .then((resp) => {
-          if (resp.status === 200) {
-            setCount(resp.result);
-          }
-        });
-    }
+    // if (props.data?.id) {
+    //   service
+    //     .productCount({
+    //       terms: [
+    //         {
+    //           terms: [
+    //             {
+    //               column: 'message_protocol',
+    //               value: props.data.id,
+    //             },
+    //           ],
+    //         },
+    //       ],
+    //     })
+    //     .then((resp) => {
+    //       if (resp.status === 200) {
+    //         setCount(resp.result);
+    //       }
+    //     });
+    // }
   }, [props.data]);
 
   const form = createForm({
@@ -200,7 +200,7 @@ const Save = (props: Props) => {
                 'x-decorator-props': {
                   gridSpan: 2,
                 },
-                'x-disabled': !!count,
+                // 'x-disabled': !!count,
                 'x-validator': [
                   {
                     required: true,

+ 41 - 3
src/pages/rule-engine/Alarm/Log/Detail/index.tsx

@@ -11,12 +11,16 @@ import { SearchOutlined } from '@ant-design/icons';
 import Info from './Info';
 import { Button } from 'antd';
 import moment from 'moment';
+import useLocation from '@/hooks/route/useLocation';
+import { useDomFullHeight } from '@/hooks';
 
 const Detail = observer(() => {
   const params = useParams<{ id: string }>();
-
+  const location = useLocation();
   const [visible, setVisible] = useState<boolean>(false);
   const [current, setCurrent] = useState<Partial<AlarmLogHistoryItem>>({});
+  const { minHeight } = useDomFullHeight(`.alarm-log`, 24);
+  const [detail, setDetail] = useState<any>({});
 
   const [param, setParam] = useState<any>({
     terms: [
@@ -91,6 +95,14 @@ const Detail = observer(() => {
     });
   }, [params.id]);
 
+  useEffect(() => {
+    const { state } = location;
+    setCurrent(detail);
+    if (state?.param.detail && detail) {
+      setVisible(true);
+    }
+  }, [location, detail]);
+
   return (
     <PageContainer>
       <SearchComponent<AlarmLogHistoryItem>
@@ -118,13 +130,39 @@ const Detail = observer(() => {
         params={param}
         columns={AlarmLogModel.columns}
         search={false}
+        tableClassName={'alarm-log'}
+        tableStyle={{ minHeight }}
         scroll={{ x: 1366 }}
-        headerTitle={'记录列表'}
+        // headerTitle={'记录列表'}
         request={async (data) => {
-          return service.queryHistoryList({
+          const res = await service.queryHistoryList({
             ...data,
             sorts: [{ name: 'alarmTime', order: 'desc' }],
           });
+          if (res.status === 200) {
+            setDetail(res.result.data[0]);
+            return {
+              code: res.message,
+              result: {
+                data: res.result.data,
+                pageIndex: res.result.pageIndex,
+                pageSize: res.result.pageSize,
+                total: res.result.total,
+              },
+              status: res.status,
+            };
+          } else {
+            return {
+              code: 200,
+              result: {
+                data: [],
+                pageIndex: 0,
+                pageSize: 0,
+                total: 0,
+              },
+              status: 200,
+            };
+          }
         }}
         rowKey="id"
       />

+ 27 - 6
src/pages/rule-engine/DashBoard/index.tsx

@@ -11,6 +11,8 @@ import styles from './index.less';
 import moment from 'moment';
 import Echarts from '@/components/DashBoard/echarts';
 import encodeQuery from '@/utils/encodeQuery';
+import useHistory from '@/hooks/route/useHistory';
+import { getMenuPathByCode } from '@/utils/menu';
 
 const service = new Service();
 export const state = model<{
@@ -43,6 +45,7 @@ type RefType = {
 };
 const Dashboard = observer(() => {
   const [options, setOptions] = useState<EChartsOption>({});
+  const history = useHistory();
 
   // 今日告警
   const today = {
@@ -253,9 +256,10 @@ const Dashboard = observer(() => {
           },
         },
         grid: {
-          left: '3%',
-          right: '4%',
-          bottom: '3%',
+          left: 0,
+          right: '1%',
+          bottom: 0,
+          top: '2%',
           containLabel: true,
         },
         xAxis: [
@@ -276,7 +280,10 @@ const Dashboard = observer(() => {
           {
             name: 'Direct',
             type: 'bar',
-            barWidth: '60%',
+            barWidth: '30%',
+            itemStyle: {
+              color: '#2F54EB',
+            },
             data: sData.reverse(),
           },
         ],
@@ -325,10 +332,24 @@ const Dashboard = observer(() => {
                         <div className={'new-alarm-item'}>
                           <div className={'new-alarm-item-time'}>
                             <img src={require('/public/images/alarm/bashboard.png')} alt="" />
-                            {moment(item.alarmTime).format('YYYY-MM-DD hh:mm:ss')}
+                            {moment(item.alarmTime).format('YYYY-MM-DD HH:mm:ss')}
                           </div>
                           <div className={'new-alarm-item-content ellipsis'}>
-                            <Tooltip title={item.alarmName}>{item.alarmName}</Tooltip>
+                            <Tooltip title={item.alarmName} placement="topLeft">
+                              <a
+                                onClick={() => {
+                                  console.log(item);
+                                  const url = getMenuPathByCode('rule-engine/Alarm/Log');
+                                  history.push(`${url}/detail/${item.id}`, {
+                                    param: {
+                                      detail: true,
+                                    },
+                                  });
+                                }}
+                              >
+                                {item.alarmName}
+                              </a>
+                            </Tooltip>
                           </div>
                           <div className={'new-alarm-item-state'}>
                             <Badge

+ 1 - 1
src/pages/system/DataSource/Management/EditTable.tsx

@@ -147,7 +147,7 @@ const EditTable = (props: Props) => {
               'x-component': 'ArrayTable.Column',
               'x-component-props': { title: '精度' },
               properties: {
-                scale: {
+                precision: {
                   type: 'number',
                   'x-decorator': 'FormItem',
                   'x-component': 'NumberPicker',