浏览代码

[monitor]feature dashboard仪表盘重构 (#13)

tomsun28 3 年之前
父节点
当前提交
f324eeaa42

+ 8 - 0
alerter/src/main/java/com/usthe/alert/controller/AlertsController.java

@@ -1,5 +1,6 @@
 package com.usthe.alert.controller;
 
+import com.usthe.alert.dto.AlertSummary;
 import com.usthe.common.entity.alerter.Alert;
 import com.usthe.alert.service.AlertService;
 import com.usthe.common.entity.dto.Message;
@@ -114,4 +115,11 @@ public class AlertsController {
         return ResponseEntity.ok(message);
     }
 
+    @GetMapping(path = "/summary")
+    @ApiOperation(value = "获取告警统计信息", notes = "获取告警统计信息")
+    public ResponseEntity<Message<AlertSummary>> getAlertsSummary() {
+        AlertSummary alertSummary = alertService.getAlertsSummary();
+        Message<AlertSummary> message = new Message<>(alertSummary);
+        return ResponseEntity.ok(message);
+    }
 }

+ 7 - 0
alerter/src/main/java/com/usthe/alert/dao/AlertDao.java

@@ -1,5 +1,6 @@
 package com.usthe.alert.dao;
 
+import com.usthe.alert.dto.AlertPriorityNum;
 import com.usthe.common.entity.alerter.Alert;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -32,4 +33,10 @@ public interface AlertDao extends JpaRepository<Alert, Long>, JpaSpecificationEx
     @Query("update Alert set status = :status where id in :ids")
     void updateAlertsStatus(@Param(value = "status") Byte status, @Param(value = "ids") List<Long> ids);
 
+    /**
+     * 查询各个告警级别的未处理告警数量
+     * @return 告警数量
+     */
+    @Query("select new com.usthe.alert.dto.AlertPriorityNum(mo.priority, count(mo.id)) from Alert mo where mo.status = 0 group by mo.priority")
+    List<AlertPriorityNum> findAlertPriorityNum();
 }

+ 18 - 0
alerter/src/main/java/com/usthe/alert/dto/AlertPriorityNum.java

@@ -0,0 +1,18 @@
+package com.usthe.alert.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 监控级别告警数量
+ * @author tom
+ * @date 2022/3/6 19:52
+ */
+@Data
+@AllArgsConstructor
+public class AlertPriorityNum {
+
+    private byte priority;
+
+    private long num;
+}

+ 39 - 0
alerter/src/main/java/com/usthe/alert/dto/AlertSummary.java

@@ -0,0 +1,39 @@
+package com.usthe.alert.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import static io.swagger.annotations.ApiModelProperty.AccessMode.READ_ONLY;
+
+/**
+ * 告警统计信息
+ * @author tom
+ * @date 2022/3/6 19:25
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@ApiModel(description = "告警统计信息")
+public class AlertSummary {
+
+    @ApiModelProperty(value = "告警总数量(包括已处理未处理告警)", example = "134", accessMode = READ_ONLY, position = 0)
+    private long total;
+
+    @ApiModelProperty(value = "已处理告警数量", example = "34", accessMode = READ_ONLY, position = 1)
+    private long dealNum;
+
+    @ApiModelProperty(value = "告警处理率", example = "39.34", accessMode = READ_ONLY, position = 2)
+    private float rate;
+
+    @ApiModelProperty(value = "告警级别为警告告警的告警数量(指未处理告警)", example = "43", accessMode = READ_ONLY, position = 3)
+    private long priorityWarningNum;
+
+    @ApiModelProperty(value = "告警级别为严重告警的告警数量(指未处理告警)", example = "56", accessMode = READ_ONLY, position = 4)
+    private long priorityCriticalNum;
+
+    @ApiModelProperty(value = "告警级别为紧急告警的告警数量(指未处理告警)", example = "23", accessMode = READ_ONLY, position = 5)
+    private long priorityEmergencyNum;
+}

+ 8 - 0
alerter/src/main/java/com/usthe/alert/service/AlertService.java

@@ -1,5 +1,6 @@
 package com.usthe.alert.service;
 
+import com.usthe.alert.dto.AlertSummary;
 import com.usthe.common.entity.alerter.Alert;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
@@ -42,4 +43,11 @@ public interface AlertService {
      * @param ids 待修改的告警IDs
      */
     void editAlertStatus(Byte status, List<Long> ids);
+
+    /**
+     * 获取告警统计信息
+     * @return 告警统计
+     */
+    AlertSummary getAlertsSummary();
+
 }

+ 38 - 0
alerter/src/main/java/com/usthe/alert/service/impl/AlertServiceImpl.java

@@ -1,8 +1,11 @@
 package com.usthe.alert.service.impl;
 
 import com.usthe.alert.dao.AlertDao;
+import com.usthe.alert.dto.AlertPriorityNum;
+import com.usthe.alert.dto.AlertSummary;
 import com.usthe.common.entity.alerter.Alert;
 import com.usthe.alert.service.AlertService;
+import com.usthe.common.util.CommonConstants;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
@@ -11,6 +14,8 @@ import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.HashSet;
 import java.util.List;
 
@@ -47,4 +52,37 @@ public class AlertServiceImpl implements AlertService {
         alertDao.updateAlertsStatus(status, ids);
     }
 
+    @Override
+    public AlertSummary getAlertsSummary() {
+        AlertSummary alertSummary = new AlertSummary();
+        List<AlertPriorityNum> priorityNums = alertDao.findAlertPriorityNum();
+        if (priorityNums != null) {
+            for (AlertPriorityNum priorityNum : priorityNums) {
+                switch (priorityNum.getPriority()) {
+                    case CommonConstants
+                            .ALERT_PRIORITY_CODE_WARNING:
+                        alertSummary.setPriorityWarningNum(priorityNum.getNum());break;
+                    case CommonConstants.ALERT_PRIORITY_CODE_CRITICAL:
+                        alertSummary.setPriorityCriticalNum(priorityNum.getNum());break;
+                    case CommonConstants.ALERT_PRIORITY_CODE_EMERGENCY:
+                        alertSummary.setPriorityEmergencyNum(priorityNum.getNum());break;
+                    default: break;
+                }
+            }
+        }
+        long total = alertDao.count();
+        long dealNum = total - alertSummary.getPriorityCriticalNum()
+                - alertSummary.getPriorityEmergencyNum() - alertSummary.getPriorityWarningNum();
+        alertSummary.setDealNum(dealNum);
+        try {
+            float rate = BigDecimal.valueOf(100 * (float) dealNum / total)
+                    .setScale(2, RoundingMode.HALF_UP)
+                    .floatValue();
+            alertSummary.setRate(rate);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return alertSummary;
+    }
+
 }

+ 4 - 4
manager/src/main/java/com/usthe/manager/dao/MonitorDao.java

@@ -55,11 +55,11 @@ public interface MonitorDao extends JpaRepository<Monitor, Long>, JpaSpecificati
     Optional<Monitor> findMonitorByNameEquals(String name);
 
     /**
-     * 查询监控类别及其对应的监控数量
-     * @return 监控类别与监控数量映射
+     * 查询监控类别-状态对应的监控数量
+     * @return 监控类别-状态与监控数量映射
      */
-    @Query("select new com.usthe.manager.pojo.dto.AppCount(mo.app, COUNT(mo.id)) from Monitor mo group by mo.app")
-    List<AppCount> findAppsCount();
+    @Query("select new com.usthe.manager.pojo.dto.AppCount(mo.app, mo.status, COUNT(mo.id)) from Monitor mo group by mo.app, mo.status")
+    List<AppCount> findAppsStatusCount();
 
     /**
      * 更新指定监控的状态

+ 38 - 3
manager/src/main/java/com/usthe/manager/pojo/dto/AppCount.java

@@ -12,8 +12,43 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @Data
 public class AppCount {
-    /**监控类型**/
+
+    public AppCount(String app, byte status, Long size) {
+        this.app = app;
+        this.status = status;
+        this.size = size;
+    }
+
+    /**
+     * 监控大类别
+     */
+    private String category;
+    /**
+     * 监控类型
+     */
     private String app;
-    /**监控数量**/
-    private Long size;
+    /**
+     * 监控状态
+     */
+    private transient byte status;
+    /**
+     * 监控数量
+     */
+    private long size;
+    /**
+     * 监控状态可用的数量
+     */
+    private long availableSize;
+    /**
+     * 监控状态未管理的数量
+     */
+    private long unManageSize;
+    /**
+     * 监控状态不可用的数量
+     */
+    private long unAvailableSize;
+    /**
+     * 监控状态不可达的数量
+     */
+    private long unReachableSize;
 }

+ 35 - 2
manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java

@@ -30,6 +30,7 @@ import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -379,8 +380,40 @@ public class MonitorServiceImpl implements MonitorService {
 
     @Override
     public List<AppCount> getAllAppMonitorsCount() {
-        return monitorDao.findAppsCount();
-
+        List<AppCount> appCounts = monitorDao.findAppsStatusCount();
+        if (appCounts == null) {
+            return null;
+        }
+        // 关联大类别信息 计算每个状态对应数量
+        Map<String, AppCount> appCountMap = new HashMap<>(appCounts.size());
+        for (AppCount item : appCounts) {
+            AppCount appCount = appCountMap.getOrDefault(item.getApp(), new AppCount());
+            appCount.setApp(item.getApp());
+            switch (item.getStatus()) {
+                case CommonConstants.AVAILABLE_CODE:
+                    appCount.setAvailableSize(appCount.getAvailableSize() + item.getSize());
+                    break;
+                case CommonConstants.UN_AVAILABLE_CODE:
+                    appCount.setUnAvailableSize(appCount.getUnAvailableSize() + item.getSize());
+                    break;
+                case CommonConstants.UN_MANAGE_CODE:
+                    appCount.setUnManageSize(appCount.getUnManageSize() + item.getSize());
+                    break;
+                case CommonConstants.UN_REACHABLE_CODE:
+                    appCount.setUnReachableSize(appCount.getUnReachableSize() + item.getSize());
+                    break;
+                default: break;
+            }
+            appCountMap.put(item.getApp(), appCount);
+        }
+        return appCountMap.values().stream().peek(item -> {
+            item.setSize(item.getAvailableSize() + item.getUnManageSize()
+                    + item.getUnReachableSize() + item.getUnAvailableSize());
+            Job job = appService.getAppDefine(item.getApp());
+            if (job != null) {
+                item.setCategory(job.getCategory());
+            }
+        }).collect(Collectors.toList());
     }
 
     @Override

+ 9 - 0
web-app/src/app/pojo/AppCount.ts

@@ -0,0 +1,9 @@
+export class AppCount {
+  category!: string;
+  app!: string;
+  size: number = 0;
+  availableSize: number = 0;
+  unManageSize: number = 0;
+  unAvailableSize: number = 0;
+  unReachableSize: number = 0;
+}

+ 158 - 2
web-app/src/app/routes/dashboard/dashboard.component.html

@@ -1,3 +1,106 @@
+<div nz-row nzGutter="16" style="margin-top: 70px">
+  <div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
+    <div nz-row nzAlign="middle" class="bg-primary rounded-lg">
+      <div nz-col nzSpan="10" class="p-md text-white">
+        <div class="h2 mt0 font-weight-bold">{{ appCountService.size }}</div>
+        <p class="h5 text-nowrap mb0">
+          <i nz-icon nzType="cloud" nzTheme="outline"></i>
+          {{ 'monitor.category.service' | i18n }}
+        </p>
+      </div>
+      <div nz-col nzSpan="14" class="p-md text-white">
+        <nz-tag class="mb-xs">
+          <span>正常 </span><span style="font-weight: bolder">{{ appCountService.availableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可用 </span><span style="font-weight: bolder">{{ appCountService.unAvailableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可达 </span><span style="font-weight: bolder">{{ appCountService.unReachableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>未监控 </span><span style="font-weight: bolder">{{ appCountService.unManageSize }}</span>
+        </nz-tag>
+      </div>
+    </div>
+  </div>
+  <div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
+    <div nz-row nzAlign="middle" class="bg-success rounded-lg">
+      <div nz-col nzSpan="10" class="p-md text-white">
+        <div class="h2 mt0 font-weight-bold">{{ appCountDb.size }}</div>
+        <p class="h5 text-nowrap mb0">
+          <i nz-icon nzType="database" nzTheme="outline"></i>
+          {{ 'monitor.category.db' | i18n }}
+        </p>
+      </div>
+      <div nz-col nzSpan="14">
+        <nz-tag class="mb-xs">
+          <span>正常 </span><span style="font-weight: bolder">{{ appCountDb.availableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可用 </span><span style="font-weight: bolder">{{ appCountDb.unAvailableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可达 </span><span style="font-weight: bolder">{{ appCountDb.unReachableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>未监控 </span><span style="font-weight: bolder">{{ appCountDb.unManageSize }}</span>
+        </nz-tag>
+      </div>
+    </div>
+  </div>
+  <div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
+    <div nz-row nzAlign="middle" class="bg-orange rounded-lg">
+      <div nz-col nzSpan="10" class="p-md text-white">
+        <div class="h2 mt0 font-weight-bold">{{ appCountOs.size }}</div>
+        <p class="h5 text-nowrap mb0">
+          <i nz-icon nzType="windows" nzTheme="outline"></i>
+          {{ 'monitor.category.os' | i18n }}
+        </p>
+      </div>
+      <div nz-col nzSpan="14">
+        <nz-tag class="mb-xs">
+          <span>正常 </span><span style="font-weight: bolder">{{ appCountOs.availableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可用 </span><span style="font-weight: bolder">{{ appCountOs.unAvailableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可达 </span><span style="font-weight: bolder">{{ appCountOs.unReachableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>未监控 </span><span style="font-weight: bolder">{{ appCountOs.unManageSize }}</span>
+        </nz-tag>
+      </div>
+    </div>
+  </div>
+  <div nz-col nzXs="24" nzSm="12" nzMd="6" class="mb-md">
+    <div nz-row nzAlign="middle" class="bg-magenta rounded-lg">
+      <div nz-col nzSpan="10" class="p-md text-white">
+        <div class="h2 mt0 font-weight-bold">{{ appCountCustom.size }}</div>
+        <p class="h5 text-nowrap mb0">
+          <i nz-icon nzType="skin" nzTheme="outline"></i>
+          {{ 'monitor.category.custom' | i18n }}
+        </p>
+      </div>
+      <div nz-col nzSpan="14">
+        <nz-tag class="mb-xs">
+          <span>正常 </span><span style="font-weight: bolder">{{ appCountCustom.availableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可用 </span><span style="font-weight: bolder">{{ appCountCustom.unAvailableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>不可达 </span><span style="font-weight: bolder">{{ appCountCustom.unReachableSize }}</span>
+        </nz-tag>
+        <nz-tag class="mb-xs">
+          <span>未监控 </span><span style="font-weight: bolder">{{ appCountCustom.unManageSize }}</span>
+        </nz-tag>
+      </div>
+    </div>
+  </div>
+</div>
+
 <div
   echarts
   [options]="appsCountEChartOption"
@@ -5,6 +108,59 @@
   [autoResize]="true"
   [loading]="appsCountLoading"
   (chartClick)="onChartClick($event)"
-  (chartInit)="onChartInit($event)"
-  style="width: 100%; height: 400px; margin-top: 5%"
+  (chartInit)="onAppsCountChartInit($event)"
+  style="width: 100%; height: 400px; margin-top: 1%"
 ></div>
+
+<div nz-row nzGutter="16" style="margin-top: 10px">
+  <div nz-col nzXs="24" nzSm="24" nzMd="12" class="mb-md">
+    <nz-card nzHoverable nzTitle="最近告警列表" [nzExtra]="extraTemplate">
+      <nz-timeline nzMode="left">
+        <nz-timeline-item *ngFor="let alert of alerts; let i = index" [nzLabel]="alert.gmtCreate.toString()">
+          <p style="font-weight: 400">
+            <nz-tag *ngIf="alert.priority == 0" nzColor="red">
+              <i nz-icon nzType="bell" nzTheme="outline"></i>
+              <span>紧急告警</span>
+            </nz-tag>
+            <nz-tag *ngIf="alert.priority == 1" nzColor="orange">
+              <i nz-icon nzType="bell" nzTheme="outline"></i>
+              <span>严重告警</span>
+            </nz-tag>
+            <nz-tag *ngIf="alert.priority == 2" nzColor="yellow">
+              <i nz-icon nzType="bell" nzTheme="outline"></i>
+              <span>警告告警</span>
+            </nz-tag>
+            <span>[{{ alert.monitorName }}] </span>
+            {{ alert.content }}
+          </p>
+        </nz-timeline-item>
+      </nz-timeline>
+    </nz-card>
+  </div>
+  <div nz-col nzXs="24" nzSm="12" nzMd="7" class="mb-md">
+    <div
+      echarts
+      [options]="alertsEChartOption"
+      theme="default"
+      [autoResize]="true"
+      [loading]="alertsLoading"
+      (chartInit)="onAlertNumChartInit($event)"
+      style="width: 100%; height: 100%"
+    ></div>
+  </div>
+  <div nz-col nzXs="24" nzSm="12" nzMd="5" class="mb-md">
+    <div
+      echarts
+      [options]="alertsDealEChartOption"
+      theme="default"
+      [autoResize]="true"
+      [loading]="alertsDealLoading"
+      (chartInit)="onAlertRateChartInit($event)"
+      style="width: 100%; height: 100%"
+    ></div>
+  </div>
+</div>
+
+<ng-template #extraTemplate>
+  <a [routerLink]="['/alert/center']">进入告警中心</a>
+</ng-template>

+ 96 - 2
web-app/src/app/routes/dashboard/dashboard.component.less

@@ -1,3 +1,97 @@
-.demo-chart {
-  height: auto;
+@import '~@delon/theme/index';
+:host ::ng-deep {
+  .ant-timeline {
+    .ant-timeline-label {
+      left: 20%;
+      width: calc(20% - 12px);
+    }
+    .ant-timeline-item-tail {
+      left: 20%;
+    }
+    .ant-timeline-item-head {
+      left: 20%;
+    }
+    .ant-timeline-item-label {
+      width: calc(20% - 12px);
+    }
+    .ant-timeline-item-left {
+      .ant-timeline-item-content {
+        left: calc(20% - 4px);
+        width: calc(80% - 14px);
+      }
+    }
+  }
+  .ant-card-head-title {
+    padding-top: 6px;
+    padding-right: 0;
+    padding-bottom: 6px;
+    padding-left: 0;
+  }
+  .ant-card-head {
+    min-height: 24px;
+    padding: 0 12px;
+    font-weight: 400;
+    font-size: 12px;
+  }
+  .ant-card-body {
+    padding-top: 24px;
+    padding-right: 24px;
+    padding-bottom: 6px;
+    padding-left: 24px;
+  }
+  .ant-timeline-item {
+    padding-bottom: 10px;
+  }
+  }
+
+[data-theme='dark'] {
+  :host ::ng-deep {
+    .ant-timeline {
+      .ant-timeline-label {
+        left: 20%;
+        width: calc(20% - 12px);
+      }
+      .ant-timeline-item-tail {
+        left: 20%;
+      }
+      .ant-timeline-item-head {
+        left: 20%;
+      }
+      .ant-timeline-item-label {
+        width: calc(20% - 12px);
+      }
+      .ant-timeline-item-left {
+        .ant-timeline-item-content {
+          left: calc(20% - 4px);
+          width: calc(80% - 14px);
+        }
+      }
+    }
+  }
+}
+
+[data-theme='compact'] {
+  :host ::ng-deep {
+    .ant-timeline {
+      .ant-timeline-label {
+        left: 20%;
+        width: calc(20% - 12px);
+      }
+      .ant-timeline-item-tail {
+        left: 20%;
+      }
+      .ant-timeline-item-head {
+        left: 20%;
+      }
+      .ant-timeline-item-label {
+        width: calc(20% - 12px);
+      }
+      .ant-timeline-item-left {
+        .ant-timeline-item-content {
+          left: calc(20% - 4px);
+          width: calc(80% - 14px);
+        }
+      }
+    }
+  }
 }

+ 241 - 8
web-app/src/app/routes/dashboard/dashboard.component.ts

@@ -6,6 +6,9 @@ import { EChartsOption } from 'echarts';
 import { NzMessageService } from 'ng-zorro-antd/message';
 import { fromEvent } from 'rxjs';
 
+import { Alert } from '../../pojo/Alert';
+import { AppCount } from '../../pojo/AppCount';
+import { AlertService } from '../../service/alert.service';
 import { MonitorService } from '../../service/monitor.service';
 
 @Component({
@@ -18,21 +21,31 @@ export class DashboardComponent implements OnInit, OnDestroy {
   constructor(
     private msg: NzMessageService,
     private monitorSvc: MonitorService,
+    private alertSvc: AlertService,
     @Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService,
     private router: Router,
     private cdr: ChangeDetectorRef
   ) {}
 
+  // start 大类别数量信息
+  appCountService: AppCount = new AppCount();
+  appCountOs: AppCount = new AppCount();
+  appCountDb: AppCount = new AppCount();
+  appCountCustom: AppCount = new AppCount();
+
+  // start 数量全局概览
   interval$!: number;
   appsCountLoading: boolean = true;
   appsCountTableData: any[] = [];
   appsCountEChartOption!: EChartsOption;
   appsCountTheme!: EChartsOption;
-  echartsInstance!: any;
+  appsCountEchartsInstance!: any;
   pageResize$!: any;
 
+  // 告警列表
+  alerts!: Alert[];
+
   ngOnInit(): void {
-    this.appsCountLoading = true;
     this.appsCountTheme = {
       title: {
         text: '监控总览',
@@ -112,8 +125,86 @@ export class DashboardComponent implements OnInit, OnDestroy {
         }
       ]
     };
+    this.alertsTheme = {
+      title: {
+        subtext: '告警等级分布',
+        left: 'center'
+      },
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          type: 'shadow'
+        }
+      },
+      xAxis: {
+        type: 'category',
+        data: ['警告告警', '严重告警', '紧急告警']
+      },
+      yAxis: {
+        type: 'value'
+      },
+      series: [
+        {
+          name: '告警数量',
+          type: 'bar',
+          data: [
+            {
+              value: 0,
+              // 设置单个柱子的样式
+              itemStyle: {
+                color: '#ffb72b',
+                shadowColor: '#91cc75'
+              }
+            },
+            {
+              value: 0,
+              itemStyle: {
+                color: '#fa6202',
+                shadowColor: '#91cc75'
+              }
+            },
+            {
+              value: 0,
+              itemStyle: {
+                color: '#dc1313',
+                shadowColor: '#91cc75'
+              }
+            }
+          ]
+        }
+      ]
+    };
+    this.alertsDealTheme = {
+      title: {
+        subtext: '告警处理',
+        left: 'center'
+      },
+      tooltip: {
+        formatter: '{b} : {c}%'
+      },
+      series: [
+        {
+          name: '告警处理率',
+          type: 'gauge',
+          progress: {
+            show: true
+          },
+          detail: {
+            valueAnimation: true,
+            formatter: '{value}'
+          },
+          data: [
+            {
+              value: 0,
+              name: '告警处理率'
+            }
+          ]
+        }
+      ]
+    };
+    this.appsCountLoading = true;
+    this.alertsLoading = true;
     this.refresh();
-    this.appsCountLoading = false;
     // https://stackoverflow.com/questions/43908009/why-is-setinterval-in-an-angular-service-only-firing-one-time
     this.interval$ = setInterval(this.refresh.bind(this), 30000);
     this.pageResize$ = fromEvent(window, 'resize').subscribe(event => {
@@ -129,12 +220,21 @@ export class DashboardComponent implements OnInit, OnDestroy {
   }
 
   refresh(): void {
+    this.refreshAppsCount();
+    this.refreshAlertContentList();
+    this.refreshAlertSummary();
+  }
+  refreshAppsCount(): void {
+    this.appCountService = new AppCount();
+    this.appCountOs = new AppCount();
+    this.appCountDb = new AppCount();
+    this.appCountCustom = new AppCount();
     let dashboard$ = this.monitorSvc.getAppsMonitorSummary().subscribe(
       message => {
         dashboard$.unsubscribe();
         if (message.code === 0 && message.data.apps != undefined) {
           // {app:'linux',size: 12}
-          let apps: any[] = message.data.apps;
+          let apps: AppCount[] = message.data.apps;
           this.appsCountTableData = [];
           let total = 0;
           apps.forEach(app => {
@@ -147,6 +247,36 @@ export class DashboardComponent implements OnInit, OnDestroy {
               value: app.size
             });
             total = total + (app.size ? app.size : 0);
+            switch (app.category) {
+              case 'service':
+                this.appCountService.size += app.size;
+                this.appCountService.availableSize += app.availableSize;
+                this.appCountService.unAvailableSize += app.unAvailableSize;
+                this.appCountService.unManageSize += app.unManageSize;
+                this.appCountService.unReachableSize += app.unReachableSize;
+                break;
+              case 'db':
+                this.appCountDb.size += app.size;
+                this.appCountDb.availableSize += app.availableSize;
+                this.appCountDb.unAvailableSize += app.unAvailableSize;
+                this.appCountDb.unManageSize += app.unManageSize;
+                this.appCountDb.unReachableSize += app.unReachableSize;
+                break;
+              case 'os':
+                this.appCountOs.size += app.size;
+                this.appCountOs.availableSize += app.availableSize;
+                this.appCountOs.unAvailableSize += app.unAvailableSize;
+                this.appCountOs.unManageSize += app.unManageSize;
+                this.appCountOs.unReachableSize += app.unReachableSize;
+                break;
+              case 'custom':
+                this.appCountCustom.size += app.size;
+                this.appCountCustom.availableSize += app.availableSize;
+                this.appCountCustom.unAvailableSize += app.unAvailableSize;
+                this.appCountCustom.unManageSize += app.unManageSize;
+                this.appCountCustom.unReachableSize += app.unReachableSize;
+                break;
+            }
           });
           // @ts-ignore
           this.appsCountTheme.series[0].data = [{ value: total, name: '监控总量' }];
@@ -158,9 +288,11 @@ export class DashboardComponent implements OnInit, OnDestroy {
           this.appsCountEChartOption = this.appsCountTheme;
           this.cdr.detectChanges();
         }
+        this.appsCountLoading = false;
       },
       error => {
         console.error(error);
+        this.appsCountLoading = false;
         dashboard$.unsubscribe();
       }
     );
@@ -175,12 +307,113 @@ export class DashboardComponent implements OnInit, OnDestroy {
     }
   }
 
-  onChartInit(ec: any) {
-    this.echartsInstance = ec;
+  onAppsCountChartInit(ec: any) {
+    this.appsCountEchartsInstance = ec;
+  }
+  onAlertNumChartInit(ec: any) {
+    this.alertsEchartsInstance = ec;
+  }
+  onAlertRateChartInit(ec: any) {
+    this.alertsDealEchartsInstance = ec;
   }
   resizeChart() {
-    if (this.echartsInstance) {
-      this.echartsInstance.resize();
+    if (this.appsCountEchartsInstance) {
+      this.appsCountEchartsInstance.resize();
+    }
+    if (this.alertsEchartsInstance) {
+      this.alertsEchartsInstance.resize();
+    }
+    if (this.alertsDealEchartsInstance) {
+      this.alertsDealEchartsInstance.resize();
     }
   }
+
+  // start 告警分布
+  alertsEChartOption!: EChartsOption;
+  alertsTheme!: EChartsOption;
+  alertsEchartsInstance!: any;
+  alertsLoading: boolean = true;
+
+  refreshAlerts(): void {
+    this.alertsEChartOption = this.alertsTheme;
+    this.cdr.detectChanges();
+    this.alertsLoading = false;
+  }
+
+  // start 告警处理率
+  alertsDealEChartOption!: EChartsOption;
+  alertsDealTheme!: EChartsOption;
+  alertsDealEchartsInstance!: any;
+  alertsDealLoading: boolean = true;
+
+  refreshAlertContentList(): void {
+    let alertsInit$ = this.alertSvc.getAlerts(0, 4).subscribe(
+      message => {
+        if (message.code === 0) {
+          let page = message.data;
+          this.alerts = page.content;
+        } else {
+          console.warn(message.msg);
+        }
+        alertsInit$.unsubscribe();
+      },
+      error => {
+        alertsInit$.unsubscribe();
+        console.error(error.msg);
+      }
+    );
+  }
+
+  refreshAlertSummary(): void {
+    let alertSummaryInit$ = this.alertSvc.getAlertsSummary().subscribe(
+      message => {
+        if (message.code === 0) {
+          let summary = message.data;
+          // @ts-ignore
+          this.alertsTheme.series[0].data = [
+            {
+              value: summary.priorityWarningNum,
+              itemStyle: {
+                color: '#ffb72b',
+                shadowColor: '#91cc75'
+              }
+            },
+            {
+              value: summary.priorityCriticalNum,
+              itemStyle: {
+                color: '#fa6202',
+                shadowColor: '#91cc75'
+              }
+            },
+            {
+              value: summary.priorityEmergencyNum,
+              itemStyle: {
+                color: '#dc1313',
+                shadowColor: '#91cc75'
+              }
+            }
+          ];
+          // @ts-ignore
+          this.alertsDealTheme.series[0].data = [
+            {
+              value: summary.rate,
+              name: '告警处理率'
+            }
+          ];
+          this.alertsEChartOption = this.alertsTheme;
+          this.alertsDealEChartOption = this.alertsDealTheme;
+          this.cdr.detectChanges();
+        } else {
+          console.warn(message.msg);
+        }
+        alertSummaryInit$.unsubscribe();
+      },
+      error => {
+        alertSummaryInit$.unsubscribe();
+        this.alertsDealLoading = false;
+        this.alertsLoading = false;
+        console.error(error.msg);
+      }
+    );
+  }
 }

+ 3 - 1
web-app/src/app/routes/routes.module.ts

@@ -10,6 +10,8 @@ import { UserLockComponent } from './passport/lock/lock.component';
 // passport pages
 import { UserLoginComponent } from './passport/login/login.component';
 import { RouteRoutingModule } from './routes-routing.module';
+import { NzTagModule } from 'ng-zorro-antd/tag';
+import { NzTimelineModule } from 'ng-zorro-antd/timeline';
 
 const COMPONENTS: Array<Type<void>> = [
   DashboardComponent,
@@ -20,7 +22,7 @@ const COMPONENTS: Array<Type<void>> = [
 ];
 
 @NgModule({
-  imports: [SharedModule, RouteRoutingModule, NgxEchartsModule],
+  imports: [SharedModule, RouteRoutingModule, NgxEchartsModule, NzTagModule, NzTimelineModule],
   declarations: COMPONENTS
 })
 export class RoutesModule {}

+ 5 - 1
web-app/src/app/service/alert.service.ts

@@ -7,7 +7,7 @@ import { Message } from '../pojo/Message';
 import { Page } from '../pojo/Page';
 
 const alerts_uri = '/alerts';
-
+const alerts_summary_uri = '/alerts/summary';
 const alerts_status_uri = '/alerts/status';
 
 @Injectable({
@@ -82,4 +82,8 @@ export class AlertService {
     const options = { params: httpParams };
     return this.http.put<Message<any>>(`${alerts_status_uri}/${status}`, null, options);
   }
+
+  public getAlertsSummary(): Observable<Message<any>> {
+    return this.http.get<Message<any>>(alerts_summary_uri);
+  }
 }

+ 1 - 1
web-app/src/assets/app-data.json

@@ -44,7 +44,7 @@
         {
           "key": "os",
           "text": "操作系统",
-          "hide": true,
+          "hide": false,
           "i18n": "menu.monitor.os",
           "icon": "anticon-windows"
         },

+ 2 - 1
web-app/src/assets/i18n/zh-CN.json

@@ -44,7 +44,8 @@
       "service": "应用服务",
       "db": "数据库",
       "os": "操作系统",
-      "mid": "中间件"
+      "mid": "中间件",
+      "custom": "自定义监控"
     },
     "app": {
       "": "监控类型",