Jelajahi Sumber

feat: 告警记录

sun-chaochao 3 tahun lalu
induk
melakukan
683ab1f900

TEMPAT SAMPAH
public/images/alarm/device.png


TEMPAT SAMPAH
public/images/alarm/level_1.png


TEMPAT SAMPAH
public/images/alarm/level_2.png


TEMPAT SAMPAH
public/images/alarm/level_3.png


TEMPAT SAMPAH
public/images/alarm/level_4.png


TEMPAT SAMPAH
public/images/alarm/level_5.png


TEMPAT SAMPAH
public/images/alarm/org.png


TEMPAT SAMPAH
public/images/alarm/other.png


TEMPAT SAMPAH
public/images/alarm/product.png


+ 4 - 4
src/pages/device/Instance/Detail/MetadataMap/EditableTable/index.tsx

@@ -48,7 +48,7 @@ const EditableCell = ({
   const save = async () => {
     try {
       const values = await form.validateFields();
-      handleSave({ ...record, metadataId: values?.metadataId });
+      handleSave({ ...record, originalId: values?.originalId });
     } catch (errInfo) {
       console.log('Save failed:', errInfo);
     }
@@ -205,14 +205,14 @@ const EditableTable = (props: Props) => {
     const newData = [...dataSource.data];
     const index = newData.findIndex((item) => row.id === item.id);
     const item = newData[index];
-    if (item?.metadataId !== row?.metadataId) {
+    if (item?.originalId !== row?.originalId) {
       const resp = await service[
         props.type === 'device' ? 'saveDeviceMetadata' : 'saveProductMetadata'
       ](props.data?.id, [
         {
           metadataType: 'property',
-          metadataId: row.id,
-          originalId: row.metadataId !== row.id ? row.metadataId : '',
+          metadataId: row.metadataId,
+          originalId: row.metadataId !== row.originalId ? row.originalId : '',
           others: {},
         },
       ]);

+ 49 - 0
src/pages/rule-engine/Alarm/Log/Detail/Info.tsx

@@ -0,0 +1,49 @@
+import { Descriptions, Modal } from 'antd';
+import { useEffect, useState } from 'react';
+import moment from 'moment';
+
+interface Props {
+  data: Partial<AlarmLogHistoryItem>;
+  close: () => void;
+}
+
+const Info = (props: Props) => {
+  const [data, setDada] = useState<Partial<AlarmLogHistoryItem>>(props.data || {});
+
+  useEffect(() => {
+    setDada(props.data);
+  }, [props.data]);
+
+  return (
+    <Modal title={'详情'} visible onCancel={props.close} onOk={props.close} width={1000}>
+      <Descriptions bordered column={2}>
+        {data.targetType === 'device' && (
+          <>
+            <Descriptions.Item label="告警设备" span={1}>
+              {data?.targetName}
+            </Descriptions.Item>
+            <Descriptions.Item label="设备ID" span={1}>
+              {data?.targetId}
+            </Descriptions.Item>
+          </>
+        )}
+        <Descriptions.Item label="告警名称" span={1}>
+          {data?.alarmConfigName}
+        </Descriptions.Item>
+        <Descriptions.Item label="告警时间" span={1}>
+          {moment(data?.alarmTime).format('YYYY-MM-DD HH:mm:ss')}
+        </Descriptions.Item>
+        <Descriptions.Item label="告警级别" span={1}>
+          {data?.level}
+        </Descriptions.Item>
+        <Descriptions.Item label="告警说明" span={1}>
+          {data?.description}
+        </Descriptions.Item>
+        <Descriptions.Item label="告警流水" span={2}>
+          {data?.alarmInfo}
+        </Descriptions.Item>
+      </Descriptions>
+    </Modal>
+  );
+};
+export default Info;

+ 133 - 0
src/pages/rule-engine/Alarm/Log/Detail/index.tsx

@@ -0,0 +1,133 @@
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { useEffect, useRef, useState } from 'react';
+import { service } from '@/pages/rule-engine/Alarm/Log';
+import { PageContainer } from '@ant-design/pro-layout';
+import { useParams } from 'umi';
+import { AlarmLogModel } from '../model';
+import { observer } from '@formily/reactive-react';
+import { SearchOutlined } from '@ant-design/icons';
+import Info from './Info';
+import { Button } from 'antd';
+import moment from 'moment';
+
+const Detail = observer(() => {
+  const params = useParams<{ id: string }>();
+
+  const [visible, setVisible] = useState<boolean>(false);
+  const [current, setCurrent] = useState<Partial<AlarmLogHistoryItem>>({});
+
+  const [param, setParam] = useState<any>({
+    terms: [
+      {
+        column: 'alarmRecordId',
+        termType: 'eq$not',
+        value: params.id || AlarmLogModel.current?.id,
+        type: 'and',
+      },
+    ],
+    sorts: [
+      {
+        name: 'name',
+        order: 'asc',
+      },
+    ],
+  });
+  const actionRef = useRef<ActionType>();
+
+  const initColumns: ProColumns<AlarmLogHistoryItem>[] = [
+    {
+      dataIndex: 'alarmTime',
+      title: '告警时间',
+      render: (text: any) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
+    },
+    {
+      dataIndex: 'alarmConfigName',
+      title: '告警名称',
+    },
+    {
+      dataIndex: 'description',
+      title: '说明',
+    },
+    {
+      dataIndex: 'action',
+      title: '操作',
+      render: (record: any) => (
+        <Button type="link">
+          <SearchOutlined
+            onClick={() => {
+              setVisible(true);
+              setCurrent(record);
+            }}
+          />
+        </Button>
+      ),
+    },
+  ];
+
+  useEffect(() => {
+    service.detail(params.id).then((resp) => {
+      if (resp.status === 200) {
+        AlarmLogModel.current = resp.result;
+        if (resp.result.targetType === 'device') {
+          initColumns.splice(2, 0, {
+            dataIndex: 'targetName',
+            title: '告警设备',
+          });
+        }
+        AlarmLogModel.columns = initColumns;
+      }
+    });
+  }, [params.id]);
+
+  return (
+    <PageContainer>
+      <SearchComponent<AlarmLogHistoryItem>
+        field={AlarmLogModel.columns}
+        target="alarm-log-detail"
+        enableSave={false}
+        onSearch={(data) => {
+          actionRef.current?.reload();
+          const terms = [
+            {
+              column: 'alarmRecordId',
+              termType: 'eq$not',
+              value: params.id || AlarmLogModel.current?.id,
+              type: 'and',
+            },
+          ];
+          setParam({
+            ...param,
+            terms: data?.terms ? [...data?.terms, ...terms] : [...terms],
+          });
+        }}
+      />
+      <ProTable<AlarmLogHistoryItem>
+        actionRef={actionRef}
+        params={param}
+        columns={AlarmLogModel.columns}
+        search={false}
+        headerTitle={'记录列表'}
+        request={async (data) => {
+          return service.queryHistoryList({
+            ...data,
+            sorts: [{ name: 'alarmTime', order: 'desc' }],
+          });
+        }}
+        rowKey="id"
+      />
+      {visible && (
+        <Info
+          close={() => {
+            setVisible(false);
+            setCurrent({});
+          }}
+          data={current}
+        />
+      )}
+    </PageContainer>
+  );
+});
+
+export default Detail;

+ 0 - 99
src/pages/rule-engine/Alarm/Log/Logger/index.tsx

@@ -1,99 +0,0 @@
-import SearchComponent from '@/components/SearchComponent';
-import type { ActionType, ProColumns } from '@jetlinks/pro-table';
-import ProTable from '@jetlinks/pro-table';
-import { useRef, useState } from 'react';
-import { service } from '@/pages/media/Cascade';
-import { PageContainer } from '@ant-design/pro-layout';
-
-interface Props {
-  data: string;
-  close: () => void;
-}
-
-const Logger = (props: Props) => {
-  const [param, setParam] = useState<any>({
-    terms: [
-      {
-        column: 'id',
-        termType: 'cascade_channel$not',
-        value: props.data,
-        type: 'and',
-      },
-      {
-        column: 'catalogType',
-        termType: 'eq',
-        value: 'device',
-        type: 'and',
-      },
-    ],
-    sorts: [
-      {
-        name: 'name',
-        order: 'asc',
-      },
-    ],
-  });
-  const actionRef = useRef<ActionType>();
-
-  const columns: ProColumns<any>[] = [
-    {
-      dataIndex: 'deviceName',
-      title: '告警时间',
-    },
-    {
-      dataIndex: 'name',
-      title: '告警名称',
-    },
-    {
-      dataIndex: 'address',
-      title: '告警设备',
-    },
-    {
-      dataIndex: 'manufacturer',
-      title: '说明',
-    },
-  ];
-
-  return (
-    <PageContainer>
-      <SearchComponent<any>
-        field={columns}
-        target="bind-channel"
-        enableSave={false}
-        onSearch={(data) => {
-          actionRef.current?.reload();
-          const terms = [
-            {
-              column: 'id',
-              termType: 'cascade_channel$not',
-              value: props.data,
-              type: 'and',
-            },
-            {
-              column: 'catalogType',
-              termType: 'eq',
-              value: 'device',
-              type: 'and',
-            },
-          ];
-          setParam({
-            ...param,
-            terms: data?.terms ? [...data?.terms, ...terms] : [...terms],
-          });
-        }}
-      />
-      <ProTable<any>
-        actionRef={actionRef}
-        params={param}
-        columns={columns}
-        search={false}
-        headerTitle={'记录列表'}
-        request={async (params) => {
-          return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });
-        }}
-        rowKey="id"
-      />
-    </PageContainer>
-  );
-};
-export default Logger;

+ 40 - 6
src/pages/rule-engine/Alarm/Log/SolveComponent/index.tsx

@@ -1,15 +1,49 @@
-import { Form, Input, Modal } from 'antd';
+import { service } from '@/pages/rule-engine/Alarm/Log';
+import { Form, Input, message, Modal } from 'antd';
+
+interface Props {
+  close: () => void;
+  reload: () => void;
+  data: Partial<AlarmLogItem>;
+}
+
+const SolveComponent = (props: Props) => {
+  const { data } = props;
+  const [form] = Form.useForm();
 
-const SolveComponent = () => {
   return (
-    <Modal title="告警处理" visible onOk={() => {}} onCancel={() => {}}>
-      <Form name="basic" layout="vertical" onFinish={() => {}}>
+    <Modal
+      title="告警处理"
+      visible
+      onOk={form.submit}
+      onCancel={() => {
+        props.close();
+      }}
+    >
+      <Form
+        name="basic"
+        layout="vertical"
+        form={form}
+        onFinish={async (values: any) => {
+          const resp = await service.handleLog(data?.id || '', {
+            describe: values.describe,
+            type: 'user',
+            state: 'normal',
+          });
+          if (resp.status === 200) {
+            message.success('操作成功!');
+            props.reload();
+          } else {
+            message.error('操作失败!');
+          }
+        }}
+      >
         <Form.Item
           label="处理结果"
-          name="username"
+          name="describe"
           rules={[{ required: true, message: '请输入处理结果!' }]}
         >
-          <Input.TextArea rows={8} placeholder="请输入处理结果" />
+          <Input.TextArea showCount maxLength={200} rows={8} placeholder="请输入处理结果" />
         </Form.Item>
       </Form>
     </Modal>

+ 23 - 27
src/pages/rule-engine/Alarm/Log/SolveLog/index.tsx

@@ -3,60 +3,59 @@ import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
 import { Modal } from 'antd';
 import { useRef, useState } from 'react';
-import { service } from '@/pages/media/Cascade';
+import { service } from '@/pages/rule-engine/Alarm/Log';
 
 interface Props {
-  data: string;
+  data: Partial<AlarmLogItem>;
   close: () => void;
 }
 
+const typeMap = new Map();
+typeMap.set('system', '系统');
+typeMap.set('user', '人工');
+
 const SolveLog = (props: Props) => {
   const [param, setParam] = useState<any>({
     terms: [
       {
-        column: 'id',
-        termType: 'cascade_channel$not',
-        value: props.data,
-        type: 'and',
-      },
-      {
-        column: 'catalogType',
+        column: 'alarmRecordId',
         termType: 'eq',
-        value: 'device',
+        value: props.data.id,
         type: 'and',
       },
     ],
     sorts: [
       {
-        name: 'name',
-        order: 'asc',
+        name: 'createTime',
+        order: 'desc',
       },
     ],
   });
   const actionRef = useRef<ActionType>();
 
-  const columns: ProColumns<any>[] = [
+  const columns: ProColumns<AlarmLogSolveHistoryItem>[] = [
     {
-      dataIndex: 'deviceName',
+      dataIndex: 'createTime',
       title: '处理时间',
     },
     {
-      dataIndex: 'name',
+      dataIndex: 'handleType',
       title: '处理类型',
+      render: (text: any) => <span>{typeMap.get(text) || ''}</span>,
     },
     {
       dataIndex: 'address',
       title: '告警时间',
     },
     {
-      dataIndex: 'manufacturer',
+      dataIndex: 'description',
       title: '告警处理',
     },
   ];
 
   return (
     <Modal title={'处理记录'} visible onCancel={props.close} onOk={() => {}} width={1200}>
-      <SearchComponent<any>
+      <SearchComponent<AlarmLogSolveHistoryItem>
         field={columns}
         target="bind-channel"
         enableSave={false}
@@ -64,15 +63,9 @@ const SolveLog = (props: Props) => {
           actionRef.current?.reload();
           const terms = [
             {
-              column: 'id',
-              termType: 'cascade_channel$not',
-              value: props.data,
-              type: 'and',
-            },
-            {
-              column: 'catalogType',
+              column: 'alarmRecordId',
               termType: 'eq',
-              value: 'device',
+              value: props.data.id,
               type: 'and',
             },
           ];
@@ -82,14 +75,17 @@ const SolveLog = (props: Props) => {
           });
         }}
       />
-      <ProTable<any>
+      <ProTable<AlarmLogSolveHistoryItem>
         actionRef={actionRef}
         params={param}
         columns={columns}
         search={false}
         headerTitle={'记录列表'}
         request={async (params) => {
-          return service.queryChannel({ ...params, sorts: [{ name: 'name', order: 'desc' }] });
+          return service.queryHandleHistory({
+            ...params,
+            sorts: [{ name: 'createTime', order: 'desc' }],
+          });
         }}
         rowKey="id"
       />

+ 40 - 40
src/pages/rule-engine/Alarm/Log/TabComponent/index.less

@@ -14,14 +14,32 @@
     box-shadow: 0 2px 16px rgba(0, 0, 0, 0.1);
 
     .alarm-log-title {
+      position: relative;
       display: flex;
       align-items: center;
       justify-content: center;
-      width: 13%;
+      width: 15%;
       padding: 10px;
+      overflow: hidden;
       color: @primary-color;
       background-color: #f0f2f3;
-      div {
+      .alarm-log-level {
+        position: absolute;
+        top: 10px;
+        right: -12px;
+        display: flex;
+        justify-content: center;
+        width: 100px;
+        padding: 2px 0;
+        color: white;
+        background-color: red;
+        transform: skewX(45deg);
+        .alarm-log-text {
+          transform: skewX(-45deg);
+        }
+      }
+      .alarm-log-title-text {
+        margin: 0 10px;
         .ellipsis();
       }
     }
@@ -40,6 +58,8 @@
 
         .alarm-type {
           max-width: 120px;
+          padding-right: 50px;
+          border-right: 1px solid rgba(0, 0, 0, 0.09);
           .name {
             color: #000;
             font-size: 18px;
@@ -52,45 +72,25 @@
             .ellipsis();
           }
         }
-      }
-      .alarm-log-time,
-      .alarm-log-level {
-        max-width: 165px;
-        margin: 0 10px;
-        .log-title {
-          margin-top: 8px;
-          color: #666;
-          font-size: 12px;
-        }
-
-        .context {
-          margin-top: 8px;
-          color: rgba(#000, 0.85);
-          font-size: 14px;
-          .ellipsis();
-        }
-      }
-
-      .alarm-log-status {
-        width: 134px;
-        height: 44px;
-        color: #666;
-        font-size: 16px;
-        line-height: 44px;
-        text-align: center;
-        background-color: #f7f7f7;
-        .icon {
-          margin-right: 10px;
-          color: #666;
-          font-size: 20px;
-        }
-      }
+        .alarm-log-right {
+          display: flex;
+          padding-left: 40px;
+          .alarm-log-time {
+            max-width: 165px;
+            margin: 0 10px;
+            .log-title {
+              margin-top: 8px;
+              color: #666;
+              font-size: 12px;
+            }
 
-      .status-active {
-        color: #f53434;
-        background-color: rgba(245, 52, 52, 0.08);
-        .icon {
-          color: #f53434;
+            .context {
+              margin-top: 8px;
+              color: rgba(#000, 0.85);
+              font-size: 14px;
+              .ellipsis();
+            }
+          }
         }
       }
 

+ 157 - 72
src/pages/rule-engine/Alarm/Log/TabComponent/index.tsx

@@ -1,57 +1,93 @@
 import SearchComponent from '@/components/SearchComponent';
-import { BellFilled, FileFilled, FileTextFilled, ToolFilled } from '@ant-design/icons';
+import { FileFilled, FileTextFilled, ToolFilled } from '@ant-design/icons';
 import type { ProColumns } from '@jetlinks/pro-table';
-import { Button, Card, Col, Empty, Pagination, Row, Space } from 'antd';
-import classNames from 'classnames';
-import moment from 'moment';
+import { Badge, Button, Card, Col, Empty, Pagination, Row, Space } from 'antd';
 import { useEffect, useState } from 'react';
 import './index.less';
 import SolveComponent from '../SolveComponent';
+import SolveLog from '../SolveLog';
 import { AlarmLogModel } from '../model';
-import Service from '../service';
-
-export const service = new Service('alarm/record');
+import moment from 'moment';
+import { observer } from '@formily/reactive-react';
+import { service } from '@/pages/rule-engine/Alarm/Log';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
+import { useHistory } from 'umi';
 
 interface Props {
   type: string;
 }
-const defaultImage = require('/public/images/alarm/log.png');
 
 const imgMap = new Map();
-imgMap.set(1, require('/public/images/alarm/level_1.png'));
-imgMap.set(2, require('/public/images/alarm/level_2.png'));
-imgMap.set(3, require('/public/images/alarm/level_3.png'));
-imgMap.set(4, require('/public/images/alarm/level_4.png'));
-imgMap.set(5, require('/public/images/alarm/level_5.png'));
+imgMap.set('product', require('/public/images/alarm/product.png'));
+imgMap.set('device', require('/public/images/alarm/device.png'));
+imgMap.set('other', require('/public/images/alarm/other.png'));
+imgMap.set('org', require('/public/images/alarm/org.png'));
+
+const titleMap = new Map();
+titleMap.set('product', '产品');
+titleMap.set('device', '设备');
+titleMap.set('other', '其他');
+titleMap.set('org', '部门');
 
-const TabComponent = (props: Props) => {
+const colorMap = new Map();
+colorMap.set(1, '#E50012');
+colorMap.set(2, '#FF9457');
+colorMap.set(3, '#FABD47');
+colorMap.set(4, '#999999');
+colorMap.set(5, '#C4C4C4');
+
+const TabComponent = observer((props: Props) => {
   const columns: ProColumns<any>[] = [
     {
       title: '名称',
       dataIndex: 'name',
     },
-    // {
-    //     title: '状态',
-    //     dataIndex: 'state',
-    //     valueType: 'select',
-    //     valueEnum: {
-    //         disabled: {
-    //             text: '已停止',
-    //             status: 'disabled',
-    //         },
-    //         enabled: {
-    //             text: '已启动',
-    //             status: 'enabled',
-    //         },
-    //     },
-    // },
-    // {
-    //     title: '说明',
-    //     dataIndex: 'description',
-    // },
+    {
+      title: '级别',
+      dataIndex: 'level',
+      valueType: 'select',
+      valueEnum: {
+        1: {
+          text: '级别一',
+          status: '1',
+        },
+        2: {
+          text: '级别二',
+          status: '2',
+        },
+        3: {
+          text: '级别三',
+          status: '3',
+        },
+        4: {
+          text: '级别四',
+          status: '4',
+        },
+        5: {
+          text: '级别五',
+          status: '5',
+        },
+      },
+    },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      valueType: 'select',
+      valueEnum: {
+        warning: {
+          text: '告警中',
+          status: 'warning',
+        },
+        normal: {
+          text: '无告警',
+          status: 'normal',
+        },
+      },
+    },
   ];
 
   const [param, setParam] = useState<any>({ pageSize: 10, terms: [] });
+  const history = useHistory<Record<string, string>>();
 
   const [dataSource, setDataSource] = useState<any>({
     data: [],
@@ -74,7 +110,7 @@ const TabComponent = (props: Props) => {
             type: 'and',
           },
         ],
-        sorts: [{ name: 'createTime', order: 'desc' }],
+        sorts: [{ name: 'alarmDate', order: 'desc' }],
       })
       .then((resp) => {
         if (resp.status === 200) {
@@ -103,73 +139,100 @@ const TabComponent = (props: Props) => {
       <Card>
         {dataSource?.data.length > 0 ? (
           <Row gutter={24} style={{ marginTop: 10 }}>
-            {(dataSource?.data || []).map((item: any, index: number) => (
+            {(dataSource?.data || []).map((item: any) => (
               <Col key={item.id} span={24}>
                 <div className="alarm-log-item">
                   <div className="alarm-log-title">
-                    <div>{item.name}</div>
+                    <div
+                      className="alarm-log-level"
+                      style={{ backgroundColor: colorMap.get(item.level) }}
+                    >
+                      <div className="alarm-log-text">
+                        {AlarmLogModel.defaultLevel.find((i) => i.level === item.level)?.title ||
+                          item.level}
+                      </div>
+                    </div>
+                    <div className="alarm-log-title-text">{item.name}</div>
                   </div>
                   <div className="alarm-log-content">
                     <div className="alarm-log-image">
                       <img
                         width={88}
                         height={88}
-                        src={defaultImage}
+                        src={imgMap.get(props.type)}
                         alt={''}
                         style={{ marginRight: 20 }}
                       />
                       <div className="alarm-type">
-                        <div className="name">产品</div>
-                        <div className="text">海康烟感A海康烟感A</div>
-                      </div>
-                    </div>
-                    <div className="alarm-log-time">
-                      <div className="log-title">最近告警时间</div>
-                      <div className="context">
-                        {moment(item.timestamp).format('YYYY-MM-DD HH:mm:ss')}
+                        <div className="name">{titleMap.get(item.targetType)}</div>
+                        <div className="text">{item.targetName}</div>
                       </div>
-                    </div>
-                    <div className="alarm-log-level">
-                      <div className="log-title">告警级别</div>
-                      <div className="context">
-                        <img src={imgMap.get(index + 1)} alt={''} style={{ marginRight: 5 }} />
-                        {item.level}
+                      <div className="alarm-log-right">
+                        <div className="alarm-log-time">
+                          <div className="log-title">最近告警时间</div>
+                          <div className="context">
+                            {moment(item.alarmDate).format('YYYY-MM-DD HH:mm:ss')}
+                          </div>
+                        </div>
+                        <div className="alarm-log-time" style={{ paddingLeft: 10 }}>
+                          <div className="log-title">状态</div>
+                          <div className="context">
+                            <Badge status={item.state.value === 'warning' ? 'error' : 'default'} />
+                            <span
+                              style={{
+                                color: item.state.value === 'warning' ? '#E50012' : 'black',
+                              }}
+                            >
+                              {item.state.text}
+                            </span>
+                          </div>
+                        </div>
                       </div>
                     </div>
-                    <div
-                      className={classNames(
-                        'alarm-log-status',
-                        item.status.value ? 'status-active' : '',
-                      )}
-                    >
-                      <BellFilled className="icon" />
-                      {item.status.text}
-                    </div>
                     <div className="alarm-log-actions">
                       <Space>
+                        {item.state.value === 'warning' && (
+                          <div className="alarm-log-action">
+                            <Button
+                              type={'link'}
+                              onClick={() => {
+                                AlarmLogModel.solveVisible = true;
+                                AlarmLogModel.current = item;
+                              }}
+                            >
+                              <div className="btn">
+                                <ToolFilled className="icon" />
+                                <div>告警处理</div>
+                              </div>
+                            </Button>
+                          </div>
+                        )}
                         <div className="alarm-log-action">
                           <Button
                             type={'link'}
                             onClick={() => {
-                              AlarmLogModel.solveVisible = true;
+                              AlarmLogModel.current = item;
+                              const url = getMenuPathByParams(
+                                MENUS_CODE['rule-engine/Alarm/Log/Detail'],
+                                item.id,
+                              );
+                              history.push(url);
                             }}
                           >
                             <div className="btn">
-                              <ToolFilled className="icon" />
-                              <div>告警处理</div>
-                            </div>
-                          </Button>
-                        </div>
-                        <div className="alarm-log-action">
-                          <Button type={'link'}>
-                            <div className="btn">
                               <FileFilled className="icon" />
                               <div>告警日志</div>
                             </div>
                           </Button>
                         </div>
                         <div className="alarm-log-action">
-                          <Button type={'link'}>
+                          <Button
+                            type={'link'}
+                            onClick={() => {
+                              AlarmLogModel.logVisible = true;
+                              AlarmLogModel.current = item;
+                            }}
+                          >
                             <div className="btn">
                               <FileTextFilled className="icon" />
                               <div>处理记录</div>
@@ -212,9 +275,31 @@ const TabComponent = (props: Props) => {
           </div>
         )}
       </Card>
-      {AlarmLogModel.solveVisible && <SolveComponent />}
+      {AlarmLogModel.solveVisible && (
+        <SolveComponent
+          close={() => {
+            AlarmLogModel.solveVisible = false;
+            AlarmLogModel.current = {};
+          }}
+          reload={() => {
+            AlarmLogModel.solveVisible = false;
+            AlarmLogModel.current = {};
+            handleSearch(param);
+          }}
+          data={AlarmLogModel.current}
+        />
+      )}
+      {AlarmLogModel.logVisible && (
+        <SolveLog
+          close={() => {
+            AlarmLogModel.logVisible = false;
+            AlarmLogModel.current = {};
+          }}
+          data={AlarmLogModel.current}
+        />
+      )}
     </div>
   );
-};
+});
 
 export default TabComponent;

+ 13 - 0
src/pages/rule-engine/Alarm/Log/index.tsx

@@ -1,7 +1,11 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import { observer } from '@formily/reactive-react';
+import { useEffect } from 'react';
 import { AlarmLogModel } from './model';
 import TabComponent from './TabComponent';
+import Service from './service';
+
+export const service = new Service('alarm/record');
 
 const Log = observer(() => {
   const list = [
@@ -22,6 +26,15 @@ const Log = observer(() => {
       tab: '其他',
     },
   ];
+
+  useEffect(() => {
+    service.queryDefaultLevel().then((resp) => {
+      if (resp.status === 200) {
+        AlarmLogModel.defaultLevel = resp.result?.levels || [];
+      }
+    });
+  }, []);
+
   return (
     <PageContainer
       onTabChange={(key: string) => {

+ 29 - 0
src/pages/rule-engine/Alarm/Log/model.ts

@@ -1,9 +1,38 @@
 import { model } from '@formily/reactive';
+import type { ProColumns } from '@jetlinks/pro-table';
 
 export const AlarmLogModel = model<{
   tab: string;
+  current: Partial<AlarmLogItem>;
   solveVisible: boolean;
+  logVisible: boolean;
+  defaultLevel: {
+    level: number;
+    title: string;
+  }[];
+  columns: ProColumns<AlarmLogHistoryItem>[];
 }>({
   tab: 'product',
+  current: {},
   solveVisible: false,
+  logVisible: false,
+  defaultLevel: [],
+  columns: [
+    {
+      dataIndex: 'alarmTime',
+      title: '告警时间',
+    },
+    {
+      dataIndex: 'alarmName',
+      title: '告警名称',
+    },
+    {
+      dataIndex: 'description',
+      title: '说明',
+    },
+    {
+      dataIndex: 'action',
+      title: '操作',
+    },
+  ],
 });

+ 23 - 0
src/pages/rule-engine/Alarm/Log/service.ts

@@ -7,6 +7,29 @@ class Service extends BaseService<AlarmLogItem> {
     request(`/${SystemConst.API_BASE}/relation/types`, {
       method: 'GET',
     });
+
+  handleLog = (id: string, data: any) =>
+    request(`/${SystemConst.API_BASE}/alarm/record/${id}/_handle`, {
+      method: 'POST',
+      data,
+    });
+
+  queryDefaultLevel = () =>
+    request(`/${SystemConst.API_BASE}/alarm/config/default/level`, {
+      method: 'GET',
+    });
+
+  queryHandleHistory = (data: any) =>
+    request(`/${SystemConst.API_BASE}/alarm/record/handle-history/_query`, {
+      method: 'POST',
+      data,
+    });
+
+  queryHistoryList = (data: any) =>
+    request(`/${SystemConst.API_BASE}/alarm/history/_query`, {
+      method: 'POST',
+      data,
+    });
 }
 
 export default Service;

+ 26 - 0
src/pages/rule-engine/Alarm/Log/typings.d.ts

@@ -1,4 +1,5 @@
 type AlarmLogItem = {
+  id?: string;
   name: string;
   alarmConfigId: string;
   alarmName: string;
@@ -10,3 +11,28 @@ type AlarmLogItem = {
   description?: string;
   state: Record<string, any>;
 };
+
+type AlarmLogSolveHistoryItem = {
+  id: string;
+  alarmId: string;
+  alarmRecordId: string;
+  handleType: string;
+  description: string;
+  creatorId?: string;
+  createTime: number;
+};
+
+type AlarmLogHistoryItem = {
+  id: string;
+  alarmId: string;
+  alarmConfigId: string;
+  alarmConfigName: string;
+  alarmRecordId: string;
+  level: number;
+  description: string;
+  alarmTime?: number;
+  targetType: string;
+  targetName: string;
+  targetId: string;
+  alarmInfo: string;
+};

+ 3 - 0
src/utils/menu/router.ts

@@ -60,6 +60,8 @@ export enum MENUS_CODE {
   'rule-engine/Instance' = 'rule-engine/Instance',
   'rule-engine/SQLRule' = 'rule-engine/SQLRule',
   'rule-engine/Scene' = 'rule-engine/Scene',
+  'rule-engine/Alarm/Log' = 'rule-engine/Alarm/Log',
+  'rule-engine/Alarm/Log/Detail' = 'rule-engine/Alarm/Log/Detail',
   'simulator/Device' = 'simulator/Device',
   'system/DataSource' = 'system/DataSource',
   'system/Department/Assets' = 'system/Department/Assets',
@@ -140,4 +142,5 @@ export const getDetailNameByCode = {
   'link/Type/Detail': '网络组件详情',
   'link/AccessConfig/Detail': '配置详情',
   'media/Stream/Detail': '流媒体详情',
+  'rule-engine/Alarm/Log/Detail': '告警日志',
 };