wzyyy 3 лет назад
Родитель
Сommit
5b440968c4

BIN
public/images/rule-engine/dashboard/ranking/1.png


BIN
public/images/rule-engine/dashboard/ranking/2.png


BIN
public/images/rule-engine/dashboard/ranking/3.png


BIN
public/images/rule-engine/dashboard/ranking/4.png


BIN
public/images/rule-engine/dashboard/ranking/5.png


BIN
public/images/rule-engine/dashboard/ranking/6.png


BIN
public/images/rule-engine/dashboard/ranking/7.png


BIN
public/images/rule-engine/dashboard/ranking/8.png


BIN
public/images/rule-engine/dashboard/ranking/9.png


+ 1 - 0
src/pages/home/components/Body.tsx

@@ -8,6 +8,7 @@ interface BodyProps {
   className?: string;
   url?: string;
 }
+
 const defaultUrl = require('/public/images/home/content.png');
 export default (props: BodyProps) => {
   return (

+ 44 - 0
src/pages/home/components/Pie.tsx

@@ -0,0 +1,44 @@
+import * as echarts from 'echarts';
+import { useEffect, useRef } from 'react';
+
+interface Props {
+  value: number;
+}
+
+const Pie = (props: Props) => {
+  const myChart: any = useRef(null);
+
+  useEffect(() => {
+    const dom = document.getElementById('charts');
+    if (dom) {
+      const option = {
+        series: [
+          {
+            type: 'pie',
+            radius: [20, 40],
+            top: 0,
+            height: 70,
+            left: 'center',
+            width: 70,
+            itemStyle: {
+              borderColor: '#fff',
+              borderWidth: 1,
+            },
+            label: {
+              show: false,
+            },
+            labelLine: {
+              show: false,
+            },
+            data: [props.value, 100 - props.value],
+          },
+        ],
+      };
+      myChart.current = myChart.current || echarts.init(dom);
+      myChart.current.setOption(option);
+    }
+  }, [props.value]);
+  return <div id="charts" style={{ width: '100%', height: 80 }}></div>;
+};
+
+export default Pie;

+ 10 - 5
src/pages/home/components/Statistics.tsx

@@ -4,13 +4,14 @@ import './index.less';
 
 type StatisticsItem = {
   name: string;
-  value: number;
-  img: string;
+  value: number | string;
+  children: React.ReactNode | string;
 };
 
 interface StatisticsProps {
   extra?: React.ReactNode | string;
   data: StatisticsItem[];
+  title: string;
 }
 
 const defaultImage = require('/public/images/home/top-1.png');
@@ -18,14 +19,18 @@ const defaultImage = require('/public/images/home/top-1.png');
 const Statistics = (props: StatisticsProps) => {
   return (
     <div className={'home-statistics'}>
-      <Title title={'设备统计'} extra={props.extra} />
+      <Title title={props.title} extra={props.extra} />
       <div className={'home-statistics-body'}>
         {props.data.map((item) => (
-          <div className={'home-guide-item'}>
+          <div className={'home-guide-item'} key={item.name}>
             <div className={'item-english'}>{item.name}</div>
             <div className={'item-title'}>{item.value}</div>
             <div className={`item-index`}>
-              <img src={item.img || defaultImage} />
+              {typeof item.children === 'string' ? (
+                <img src={item.children || defaultImage} />
+              ) : (
+                item.children
+              )}
             </div>
           </div>
         ))}

+ 1 - 0
src/pages/home/components/index.less

@@ -1,4 +1,5 @@
 @import '~antd/es/style/themes/default.less';
+
 @bodyPadding: 24px 16px;
 @margin: 24px;
 

+ 221 - 1
src/pages/home/comprehensive/index.tsx

@@ -1,4 +1,224 @@
+import { PermissionButton } from '@/components';
+import useHistory from '@/hooks/route/useHistory';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import { Col, message, Row } from 'antd';
+import Body from '../components/Body';
+import Guide from '../components/Guide';
+import Statistics from '../components/Statistics';
+import Steps from '../components/Steps';
+import { service } from '..';
+import { useEffect, useState } from 'react';
+import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
+import { map } from 'rxjs';
+import Pie from '../components/Pie';
+
 const Comprehensive = () => {
-  return <div>综合管理视图</div>;
+  const [subscribeTopic] = useSendWebsocketMessage();
+  const productPermission = PermissionButton.usePermission('device/Product').permission;
+  const devicePermission = PermissionButton.usePermission('device/Instance').permission;
+  const rulePermission = PermissionButton.usePermission('rule-engine/Instance').permission;
+
+  const [productCount, setProductCount] = useState<number>(0);
+  const [deviceCount, setDeviceCount] = useState<number>(0);
+  const [cpuValue, setCpuValue] = useState<number>(0);
+  const [jvmValue, setJvmValue] = useState<number>(0);
+
+  const getProductCount = async () => {
+    const resp = await service.productCount({});
+    if (resp.status === 200) {
+      setProductCount(resp.result);
+    }
+  };
+
+  const getDeviceCount = async () => {
+    const resp = await service.deviceCount();
+    if (resp.status === 200) {
+      setDeviceCount(resp.result);
+    }
+  };
+
+  // websocket
+  useEffect(() => {
+    getProductCount();
+    getDeviceCount();
+  }, []);
+
+  useEffect(() => {
+    const cpuRealTime = subscribeTopic!(
+      `operations-statistics-system-info-cpu-realTime`,
+      `/dashboard/systemMonitor/stats/info/realTime`,
+      {
+        type: 'cpu',
+        interval: '2s',
+        agg: 'avg',
+      },
+    )
+      ?.pipe(map((res) => res.payload))
+      .subscribe((payload: any) => {
+        setCpuValue(payload.value?.systemUsage || 0);
+      });
+
+    const jvmRealTime = subscribeTopic!(
+      `operations-statistics-system-info-memory-realTime`,
+      `/dashboard/systemMonitor/stats/info/realTime`,
+      {
+        type: 'memory',
+        interval: '2s',
+        agg: 'avg',
+      },
+    )
+      ?.pipe(map((res) => res.payload))
+      .subscribe((payload: any) => {
+        setJvmValue(payload.value?.jvmHeapUsage || 0);
+      });
+
+    return () => {
+      cpuRealTime?.unsubscribe();
+      jvmRealTime?.unsubscribe();
+    };
+  }, []);
+
+  const history = useHistory();
+  // // 跳转
+
+  const guideList = [
+    {
+      key: 'product',
+      name: '创建产品',
+      english: 'CREATE PRODUCT',
+      auth: !!productPermission.add,
+      url: 'device/Product',
+      param: '?save=true',
+    },
+    {
+      key: 'device',
+      name: '创建设备',
+      english: 'CREATE DEVICE',
+      auth: !!devicePermission.add,
+      url: 'device/Instance',
+      param: '?save=true',
+    },
+    {
+      key: 'rule-engine',
+      name: '规则引擎',
+      english: 'RULE ENGINE',
+      auth: !!rulePermission.add,
+      url: 'rule-engine/Instance',
+      param: '?save=true',
+    },
+  ];
+
+  const guideOpsList = [
+    {
+      key: 'product',
+      name: '设备接入配置',
+      english: 'CREATE PRODUCT',
+      auth: !!productPermission.add,
+      url: 'device/Product',
+      param: '?save=true',
+    },
+    {
+      key: 'device',
+      name: '日志排查',
+      english: 'CREATE DEVICE',
+      auth: !!devicePermission.add,
+      url: 'device/Instance',
+      param: '?save=true',
+    },
+    {
+      key: 'rule-engine',
+      name: '实时监控',
+      english: 'RULE ENGINE',
+      auth: !!rulePermission.add,
+      url: 'rule-engine/Instance',
+      param: '?save=true',
+    },
+  ];
+
+  return (
+    <Row gutter={24}>
+      <Col span={14}>
+        <Guide title="物联网引导" data={guideList} />
+      </Col>
+      <Col span={10}>
+        <Statistics
+          data={[
+            {
+              name: '产品数量',
+              value: productCount,
+              children: '',
+            },
+            {
+              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={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 />
+      </Col>
+      <Col span={24}>
+        <Steps />
+      </Col>
+    </Row>
+  );
 };
 export default Comprehensive;

+ 51 - 6
src/pages/home/device/index.tsx

@@ -1,13 +1,41 @@
-import { Col, Row } from 'antd';
+import { Col, message, Row } from 'antd';
 import { PermissionButton } from '@/components';
-import { Guide, 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';
+import { useHistory } from '@/hooks';
+import { service } from '..';
+import { useEffect, useState } from 'react';
 
 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 [productCount, setProductCount] = useState<number>(0);
+  const [deviceCount, setDeviceCount] = useState<number>(0);
+
+  const getProductCount = async () => {
+    const resp = await service.productCount({});
+    if (resp.status === 200) {
+      setProductCount(resp.result);
+    }
+  };
+
+  const getDeviceCount = async () => {
+    const resp = await service.deviceCount();
+    if (resp.status === 200) {
+      setDeviceCount(resp.result);
+    }
+  };
+
+  useEffect(() => {
+    getProductCount();
+    getDeviceCount();
+  }, []);
+
+  const history = useHistory();
   // // 跳转
 
   const guideList = [
@@ -75,15 +103,32 @@ const Device = () => {
           data={[
             {
               name: '产品数量',
-              value: 111,
-              img: '',
+              value: productCount,
+              children: '',
             },
             {
               name: '设备数量',
-              value: 12,
-              img: '',
+              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}>

+ 1 - 1
src/pages/home/index.tsx

@@ -9,7 +9,7 @@ import Service from './service';
 export const service = new Service();
 const Home = () => {
   type ViewType = keyof typeof ViewMap;
-  const [current, setCurrent] = useState<ViewType>('init');
+  const [current, setCurrent] = useState<ViewType>('comprehensive');
 
   const ViewMap = {
     init: <Init changeView={(value: ViewType) => setCurrent(value)} />,

+ 9 - 0
src/pages/home/service.ts

@@ -12,6 +12,15 @@ class Service {
       method: 'POST',
       data,
     });
+  // 设备数量
+  deviceCount = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/device/instance/_count`, { methods: 'GET', params: data });
+  // 产品数量
+  productCount = (data?: any) =>
+    request(`/${SystemConst.API_BASE}/device-product/_count`, {
+      method: 'POST',
+      data,
+    });
 }
 
 export default Service;

+ 136 - 90
src/pages/rule-engine/DashBoard/index.tsx

@@ -10,6 +10,7 @@ import DashBoard, { DashBoardTopCard } from '@/components/DashBoard';
 import { FireOutlined } from '@ant-design/icons';
 import styles from './index.less';
 import moment from 'moment';
+import Echarts from '@/components/DashBoard/echarts';
 
 const service = new Service();
 export const state = model<{
@@ -19,6 +20,8 @@ export const state = model<{
   enabledConfig: number;
   disabledConfig: number;
   alarmList: any[];
+  ranking: { targetId: string; targetName: string; count: number }[];
+  fifteenOptions: any;
 }>({
   today: 0,
   thisMonth: 0,
@@ -26,6 +29,8 @@ export const state = model<{
   enabledConfig: 0,
   disabledConfig: 0,
   alarmList: [],
+  ranking: [],
+  fifteenOptions: {},
 });
 
 type DashboardItem = {
@@ -70,44 +75,63 @@ const Dashboard = observer(() => {
       from: 'now-1M',
     },
   };
-  // 告警趋势
-  const chartData = {
+
+  const fifteen = {
     dashboard: 'alarm',
     object: 'record',
     measurement: 'trend',
     dimension: 'agg',
-    group: 'alarmTrend',
-    params: {
-      time: '1M',
-      targetType: 'device', // product、device、org、other
-      from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
-      format: 'M月',
-      to: 'now',
-      limit: 12,
-    },
-  };
-  // 告警排名
-  const order = {
-    dashboard: 'alarm',
-    object: 'record',
-    measurement: 'rank',
-    dimension: 'agg',
-    group: 'alarmRank',
+    group: '15day',
     params: {
-      time: '1h',
-      targetType: 'device',
-      from: 'now-1w',
+      time: '1d',
+      format: 'yyyy-MM-dd',
+      targetType: 'product',
+      from: 'now-15d',
       to: 'now',
-      limit: 10,
+      limit: 15,
     },
   };
 
   const getDashboard = async () => {
-    const resp = await service.dashboard([today, thisMonth, order]);
+    const resp = await service.dashboard([today, thisMonth, fifteen]);
     if (resp.status === 200) {
       const _data = resp.result as DashboardItem[];
       state.today = _data.find((item) => item.group === 'today')?.data.value;
       state.thisMonth = _data.find((item) => item.group === 'thisMonth')?.data.value;
+
+      const fifteenData = _data.filter((item) => item.group === '15day').map((item) => item.data);
+      state.fifteenOptions = {
+        xAxis: {
+          type: 'category',
+          data: fifteenData.map((item) => item.timeString),
+          show: false,
+        },
+        yAxis: {
+          type: 'value',
+          show: false,
+        },
+        grid: {
+          top: '2%',
+          bottom: 0,
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: {
+            type: 'shadow',
+          },
+        },
+        series: [
+          {
+            name: '告警数',
+            data: fifteenData.map((item, index) => index * 6),
+
+            type: 'bar',
+            itemStyle: {
+              color: '#2F54EB',
+            },
+          },
+        ],
+      };
     }
   };
 
@@ -140,22 +164,78 @@ const Dashboard = observer(() => {
     }
   };
 
+  const getCurrentAlarm = async () => {
+    const alarmLevel = await service.getAlarmLevel();
+
+    const currentAlarm = await service.getAlarm({});
+    if (currentAlarm.status === 200) {
+      if (alarmLevel.status === 200) {
+        const levels = alarmLevel.result.levels;
+        state.alarmList = currentAlarm.result?.data.map((item: { level: any }) => ({
+          ...item,
+          level: levels.find((l: any) => l.level === item.level)?.title,
+        }));
+      } else {
+        state.alarmList = currentAlarm.result?.data;
+      }
+    }
+  };
   useEffect(() => {
     getDashboard();
     getAlarmConfig();
+    getCurrentAlarm();
   }, []);
 
-  const getEcharts = async () => {
+  const getEcharts = async (params: any) => {
+    // 告警趋势
+    const chartData = {
+      dashboard: 'alarm',
+      object: 'record',
+      measurement: 'trend',
+      dimension: 'agg',
+      group: 'alarmTrend',
+      params: {
+        targetType: 'device', // product、device、org、other
+        format: 'yyyy年-M月',
+        time: '1M',
+        // from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
+        // to: 'now',
+        limit: 12,
+        // time: params.time.type === 'today' ? '1h' : '1d',
+        from: moment(params.time.start).format('YYYY-MM-DD HH:mm:ss'),
+        to: moment(params.time.end).format('YYYY-MM-DD HH:mm:ss'),
+        // limit: 30,
+      },
+    };
+
+    // 告警排名
+    const order = {
+      dashboard: 'alarm',
+      object: 'record',
+      measurement: 'rank',
+      dimension: 'agg',
+      group: 'alarmRank',
+      params: {
+        // time: '1h',
+        time: params.time.type === 'today' ? '1h' : '1d',
+        targetType: 'device',
+        from: moment(params.time.start).format('YYYY-MM-DD HH:mm:ss'),
+        to: moment(params.time.end).format('YYYY-MM-DD HH:mm:ss'),
+        limit: 9,
+      },
+    };
     // 请求数据
-    const resp = await service.dashboard([chartData]);
+    const resp = await service.dashboard([chartData, order]);
 
-    if (resp.status === 200) {
+    if (resp?.status === 200) {
       const xData: string[] = [];
       const sData: number[] = [];
-      resp.result.forEach((item: any) => {
-        xData.push(item.data.timeString);
-        sData.push(item.data.value);
-      });
+      resp.result
+        .filter((item: any) => item.group === 'alarmTrend')
+        .forEach((item: any) => {
+          xData.push(item.data.timeString);
+          sData.push(item.data.value);
+        });
       setOptions({
         tooltip: {
           trigger: 'axis',
@@ -192,6 +272,11 @@ const Dashboard = observer(() => {
           },
         ],
       });
+
+      state.ranking = resp.result
+        ?.filter((item: any) => item.group === 'alarmRank')
+        .map((d: { data: { value: any } }) => d.data?.value)
+        .sort((a: { count: number }, b: { count: number }) => b.count - a.count);
     }
   };
 
@@ -206,7 +291,7 @@ const Dashboard = observer(() => {
             footer={[{ title: '当月告警', value: state.thisMonth, status: 'success' }]}
             span={6}
           >
-            <img src={require('/public/images/media/dashboard-1.png')} />
+            <Echarts options={state.fifteenOptions} />
           </DashBoardTopCard.Item>
           <DashBoardTopCard.Item
             title="告警配置"
@@ -226,35 +311,17 @@ const Dashboard = observer(() => {
                 <div className={'content-left-title'}>最新告警</div>
                 <div>
                   <ul>
-                    {[
-                      {
-                        dateTime: '2022-01-01 00:00:00',
-                        name: '一楼烟感告警',
-                        product: '产品',
-                        level: '1极告警',
-                      },
-                      {
-                        dateTime: '2022-01-01 00:00:00',
-                        name: '一楼烟感告警',
-                        product: '产品',
-                        level: '1极告警',
-                      },
-                      {
-                        dateTime: '2022-01-01 00:00:00',
-                        name: '一楼烟感告警',
-                        product: '产品',
-                        level: '1极告警',
-                      },
-                    ].map((item) => (
-                      <li>
+                    {state.alarmList.slice(0, 3).map((item) => (
+                      <li key={item.id}>
                         <div
                           style={{ display: 'flex', justifyContent: 'space-between', margin: 10 }}
                         >
                           <div>
-                            <FireOutlined style={{ marginRight: 5 }} /> {item.dateTime}
+                            <FireOutlined style={{ marginRight: 5 }} />{' '}
+                            {moment(item.alarmTime).format('YYYY-MM-DD hh:mm:ss')}
                           </div>
-                          <div>{item.name}</div>
-                          <div>{item.product}</div>
+                          <div>{item.alarmName}</div>
+                          <div>{item.state?.text}</div>
                           <div>{item.level}</div>
                         </div>
                       </li>
@@ -269,7 +336,7 @@ const Dashboard = observer(() => {
       <Card style={{ marginTop: 10 }}>
         <DashBoard
           title="告警统计"
-          height={400}
+          height={600}
           options={options}
           onParamsChange={getEcharts}
           ref={alarmCountRef}
@@ -277,40 +344,19 @@ const Dashboard = observer(() => {
             <div className={styles.alarmRank}>
               <h4>告警排名</h4>
               <ul className={styles.rankingList}>
-                {[
-                  {
-                    dateTime: '2022-01-01 00:00:00',
-                    name: '一楼烟感告警',
-                    product: '产品',
-                    level: '543',
-                  },
-                  {
-                    dateTime: '2022-01-01 00:00:00',
-                    name: '一楼烟感告警',
-                    product: '产品',
-                    level: '3445',
-                  },
-                  {
-                    dateTime: '2022-01-01 00:00:00',
-                    name: '一楼烟感告警',
-                    product: '产品',
-                    level: '123123',
-                  },
-                  {
-                    dateTime: '2022-01-01 00:00:00',
-                    name: '一楼烟感告警',
-                    product: '产品',
-                    level: '3123',
-                  },
-                ].map((item, i) => (
-                  <li key={item.dateTime}>
-                    <span className={`${styles.rankingItemNumber} ${i < 3 ? styles.active : ''}`}>
-                      {i + 1}
-                    </span>
-                    <span className={styles.rankingItemTitle} title={item.name}>
-                      {item.name}
+                {state.ranking?.map((item, i) => (
+                  <li key={item.targetId}>
+                    <img
+                      src={require(`/public/images/rule-engine/dashboard/ranking/${i + 1}.png`)}
+                      alt=""
+                    />
+                    {/*<span className={`${styles.rankingItemNumber} ${i < 3 ? styles.active : ''}`}>*/}
+                    {/*  {i + 1}*/}
+                    {/*</span>*/}
+                    <span className={styles.rankingItemTitle} title={item.targetName}>
+                      {item.targetName}
                     </span>
-                    <span className={styles.rankingItemValue}>{item.level}</span>
+                    <span className={styles.rankingItemValue}>{item.count}</span>
                   </li>
                 ))}
               </ul>

+ 5 - 0
src/pages/rule-engine/DashBoard/service.ts

@@ -19,6 +19,11 @@ class Service {
       method: 'POST',
       data,
     });
+
+  getAlarmLevel = () =>
+    request(`/${SystemConst.API_BASE}/alarm/config/default/level`, {
+      method: 'GET',
+    });
 }
 
 export default Service;