فهرست منبع

[alert,webapp] 告警中心条件查询与搜索支持,支持批量已读未读

tomsun28 4 سال پیش
والد
کامیت
d7a7c11ed9

+ 0 - 1
alerter/src/main/java/com/usthe/alert/controller/AlertDefineController.java

@@ -21,7 +21,6 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.validation.Valid;
 
 import java.util.List;
-import java.util.Map;
 
 import static com.usthe.common.util.CommonConstants.MONITOR_NOT_EXIST_CODE;
 import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

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

@@ -14,6 +14,8 @@ import org.springframework.data.jpa.domain.Specification;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.DeleteMapping;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PutMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -97,4 +99,16 @@ public class AlertsController {
         return ResponseEntity.ok(message);
     }
 
+    @PutMapping(path = "/status/{status}")
+    @ApiOperation(value = "批量修改告警状态", notes = "批量修改告警状态,设置已读未读")
+    public ResponseEntity<Message<Void>> applyAlertDefinesStatus(
+            @ApiParam(value = "告警状态值", example = "0") @PathVariable Byte status,
+            @ApiParam(value = "告警IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids) {
+        if (ids != null && status != null && !ids.isEmpty()) {
+            alertService.editAlertStatus(status, ids);
+        }
+        Message<Void> message = new Message<>();
+        return ResponseEntity.ok(message);
+    }
+
 }

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

@@ -3,7 +3,11 @@ package com.usthe.alert.dao;
 import com.usthe.alert.pojo.entity.Alert;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -19,4 +23,13 @@ public interface AlertDao extends JpaRepository<Alert, Long>, JpaSpecificationEx
      */
     void deleteAlertsByIdIn(Set<Long> alertIds);
 
+    /**
+     * 根据告警ID-状态值 更新告警状态
+     * @param status 状态值
+     * @param ids 告警ID列表
+     */
+    @Modifying
+    @Query("update Alert set status = :status where id in :ids")
+    void updateAlertsStatus(@Param(value = "status") Byte status, @Param(value = "ids") List<Long> ids);
+
 }

+ 1 - 1
alerter/src/main/java/com/usthe/alert/pojo/entity/Alert.java

@@ -67,7 +67,7 @@ public class Alert {
     @Length(max = 1024)
     private String content;
 
-    @ApiModelProperty(value = "告警状态: 0-正常告警 1-触发中:阈值触发但未达到告警次数 2-恢复告警",
+    @ApiModelProperty(value = "告警状态: 0-正常告警(未读) 1-阈值触发但未达到告警次数 2-恢复告警 3-已读已知",
             example = "1", accessMode = READ_WRITE, position = 7)
     @Min(0)
     @Max(2)

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

@@ -6,6 +6,7 @@ import org.springframework.data.domain.PageRequest;
 import org.springframework.data.jpa.domain.Specification;
 
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * 告警信息管理接口
@@ -34,4 +35,11 @@ public interface AlertService {
      * @param ids 告警IDs
      */
     void deleteAlerts(HashSet<Long> ids);
+
+    /**
+     * 根据告警ID-状态值 更新告警状态
+     * @param status 待修改为的告警状态
+     * @param ids 待修改的告警IDs
+     */
+    void editAlertStatus(Byte status, List<Long> ids);
 }

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

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.HashSet;
+import java.util.List;
 
 /**
  * 告警信息服务实现
@@ -40,4 +41,10 @@ public class AlertServiceImpl implements AlertService {
     public void deleteAlerts(HashSet<Long> ids) {
         alertDao.deleteAlertsByIdIn(ids);
     }
+
+    @Override
+    public void editAlertStatus(Byte status, List<Long> ids) {
+        alertDao.updateAlertsStatus(status, ids);
+    }
+
 }

+ 1 - 1
manager/src/main/resources/db/schema.sql

@@ -116,7 +116,7 @@ CREATE TABLE  alert
     alert_define_id  bigint           not null comment '告警关联的告警定义ID',
     priority         tinyint          not null default 0 comment '告警级别 0:高-emergency-紧急告警-红色 1:中-critical-严重告警-橙色 2:低-warning-警告告警-黄色',
     content          varchar(255)     not null comment '告警通知实际内容',
-    status           tinyint          not null default 0 comment '告警状态: 0-正常告警 1-阈值触发但未达到告警次数 2-恢复告警',
+    status           tinyint          not null default 0 comment '告警状态: 0-正常告警(未读) 1-阈值触发但未达到告警次数 2-恢复告警 3-已读已知',
     times            int              not null comment '触发次数,即达到告警定义的触发阈值次数要求后才会发告警',
     gmt_create       timestamp        default current_timestamp comment 'create time',
     primary key (id)

+ 2 - 0
web-app/src/app/pojo/Alert.ts

@@ -3,7 +3,9 @@ export class Alert {
   target!: string;
   monitorId!: number;
   monitorName!: string;
+  // 告警级别 0:高-emergency-紧急告警-红色 1:中-critical-严重告警-橙色 2:低-warning-警告告警-黄色
   priority: number = 2;
+  // 告警状态: 0-正常告警(未读)  3-已读已知
   status!: number;
   content!: string;
   times!: number;

+ 48 - 8
web-app/src/app/routes/alert/alert-center/alert-center.component.html

@@ -13,13 +13,43 @@
 </nz-breadcrumb>
 <nz-divider></nz-divider>
 
-<button nz-button nzType="primary" (click)="onDeleteAlerts()">
-  <i nz-icon nzType="delete" nzTheme="outline"></i>
-  删除告警
-</button>
-<button nz-button nzType="primary" (click)="sync()" nz-tooltip nzTooltipTitle="刷新">
-  <i nz-icon nzType="sync" nzTheme="outline"></i>
-</button>
+<div>
+  <button nz-button nzType="primary" (click)="onDeleteAlerts()">
+    <i nz-icon nzType="delete" nzTheme="outline"></i>
+    删除告警
+  </button>
+  <button nz-button nzType="primary" (click)="onMarkReadAlerts()">
+    <i nz-icon nzType="down-circle" nzTheme="outline"></i>
+    标记已读
+  </button>
+  <button nz-button nzType="primary" (click)="onMarkUnReadAlerts()">
+    <i nz-icon nzType="up-circle" nzTheme="outline"></i>
+    标记未读
+  </button>
+  <button nz-button nzType="primary" (click)="sync()" nz-tooltip nzTooltipTitle="刷新">
+    <i nz-icon nzType="sync" nzTheme="outline"></i>
+  </button>
+
+  <button style="margin-right: 25px;float: right;" nz-button nzType="primary" (click)="onFilterSearchAlerts()">
+    搜索
+  </button>
+  <input style="margin-right: 5px;float: right;width:150px;border-radius: 9px;text-align: center;" nz-input
+         type="text" placeholder="搜索告警内容" nzSize="default" [(ngModel)]="filterContent" />
+  <nz-select style="margin-right: 10px;float: right;width: 120px;" nzAllowClear
+             [nzPlaceHolder]="'告警状态过滤'" [(ngModel)]="filterStatus">
+    <nz-option nzLabel="全部状态" nzValue="9"></nz-option>
+    <nz-option nzLabel="未读告警" nzValue="0"></nz-option>
+    <nz-option nzLabel="已读告警" nzValue="3"></nz-option>
+  </nz-select>
+  <nz-select style="margin-right: 10px;float: right;width: 120px;" nzAllowClear
+             [nzPlaceHolder]="'告警级别过滤'" [(ngModel)]="filterPriority">
+    <nz-option nzLabel="全部级别" nzValue="9"></nz-option>
+    <nz-option nzLabel="警告级别" nzValue="2"></nz-option>
+    <nz-option nzLabel="严重级别" nzValue="1"></nz-option>
+    <nz-option nzLabel="紧急级别" nzValue="0"></nz-option>
+  </nz-select>
+
+</div>
 
 <nz-table #fixedTable [nzData]="alerts"
           [nzPageIndex]="pageIndex" [nzPageSize]="pageSize" [nzTotal]="total"
@@ -37,12 +67,13 @@
     <th nzAlign="center">所属监控</th>
     <th nzAlign="center">级别</th>
     <th nzAlign="center">告警内容</th>
+    <th nzAlign="center">状态</th>
     <th nzAlign="center">告警时间</th>
     <th nzAlign="center" nzRight>操作</th>
   </tr>
   </thead>
   <tbody>
-  <tr *ngFor="let data of fixedTable.data">
+  <tr *ngFor="let data of fixedTable.data" [ngStyle]="{'background-color':data.status === 0 ? '#E1E7A89B' : 'inherit' }">
     <td nzAlign="center" nzLeft [nzChecked]="checkedAlertIds.has(data.id)" (nzCheckedChange)="onItemChecked(data.id, $event)"></td>
     <td nzAlign="center">{{ data.target }}</td>
     <td nzAlign="center">
@@ -65,11 +96,20 @@
       </nz-tag>
     </td>
     <td nzAlign="center">{{ data.content }}</td>
+    <td nzAlign="center">
+      {{ data.status === 0 ? '未读' : '已读' }}
+    </td>
     <td nzAlign="center">{{ data.gmtCreate }}</td>
     <td nzAlign="center" nzRight>
       <button nz-button nzType="primary" (click)="onDeleteOneAlert(data.id)" nz-tooltip nzTooltipTitle="删除告警">
         <i nz-icon nzType="delete" nzTheme="outline"></i>
       </button>
+      <button nz-button nzType="primary" (click)="onMarkReadOneAlert(data.id)" nz-tooltip nzTooltipTitle="标记已读">
+        <i nz-icon nzType="down-circle" nzTheme="outline"></i>
+      </button>
+      <button nz-button nzType="primary" (click)="onMarkUnReadOneAlert(data.id)" nz-tooltip nzTooltipTitle="标记未读">
+        <i nz-icon nzType="up-circle" nzTheme="outline"></i>
+      </button>
     </td>
   </tr>
   </tbody>

+ 106 - 2
web-app/src/app/routes/alert/alert-center/alert-center.component.ts

@@ -23,11 +23,40 @@ export class AlertCenterComponent implements OnInit {
   alerts!: Alert[];
   tableLoading: boolean = false;
   checkedAlertIds = new Set<number>();
+  // 搜索过滤相关属性
+  filterStatus: number | undefined;
+  filterPriority: number | undefined;
+  filterContent: string | undefined;
 
   ngOnInit(): void {
     this.loadAlertsTable();
   }
 
+  onFilterSearchAlerts() {
+    this.tableLoading = true;
+    let filterAlerts$ = this.alertSvc.searchAlerts(this.filterStatus, this.filterPriority,
+      this.filterContent, this.pageIndex - 1, this.pageSize)
+      .subscribe(message => {
+        filterAlerts$.unsubscribe();
+        this.tableLoading = false;
+        this.checkedAll = false;
+        this.checkedAlertIds.clear();
+        if (message.code === 0) {
+          let page = message.data;
+          this.alerts = page.content;
+          this.pageIndex = page.number + 1;
+          this.total = page.totalElements;
+        } else {
+          console.warn(message.msg);
+        }
+      }, error => {
+        this.tableLoading = false;
+        filterAlerts$.unsubscribe();
+        console.error(error.msg);
+      });
+
+  }
+
   sync() {
     this.loadAlertsTable();
   }
@@ -51,6 +80,7 @@ export class AlertCenterComponent implements OnInit {
       }, error => {
         this.tableLoading = false;
         alertsInit$.unsubscribe();
+        console.error(error.msg);
       });
   }
 
@@ -69,6 +99,35 @@ export class AlertCenterComponent implements OnInit {
     });
   }
 
+  onMarkReadAlerts() {
+    if (this.checkedAlertIds == null || this.checkedAlertIds.size === 0) {
+      this.notifySvc.warning("未选中任何待标记项!","");
+      return;
+    }
+    this.modal.confirm({
+      nzTitle: '请确认是否批量标记已读!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.updateAlertsStatus(this.checkedAlertIds, 3)
+    });
+  }
+  onMarkUnReadAlerts() {
+    if (this.checkedAlertIds == null || this.checkedAlertIds.size === 0) {
+      this.notifySvc.warning("未选中任何待标记项!","");
+      return;
+    }
+    this.modal.confirm({
+      nzTitle: '请确认是否批量标记未读!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.updateAlertsStatus(this.checkedAlertIds, 0)
+    });
+  }
+
   onDeleteOneAlert(alertId: number) {
     let alerts = new Set<number>();
     alerts.add(alertId);
@@ -82,6 +141,32 @@ export class AlertCenterComponent implements OnInit {
     });
   }
 
+  onMarkReadOneAlert(alertId: number) {
+    let alerts = new Set<number>();
+    alerts.add(alertId);
+    this.modal.confirm({
+      nzTitle: '请确认是否标记已读!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.updateAlertsStatus(alerts, 3)
+    });
+  }
+
+  onMarkUnReadOneAlert(alertId: number) {
+    let alerts = new Set<number>();
+    alerts.add(alertId);
+    this.modal.confirm({
+      nzTitle: '请确认是否标记未读!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.updateAlertsStatus(alerts, 0)
+    });
+  }
+
   deleteAlerts(alertIds: Set<number>) {
     this.tableLoading = true;
     const deleteAlerts$ = this.alertSvc.deleteAlerts(alertIds)
@@ -103,6 +188,27 @@ export class AlertCenterComponent implements OnInit {
       );
   }
 
+  updateAlertsStatus(alertIds: Set<number>, status: number) {
+    this.tableLoading = true;
+    const markAlertsStatus$ = this.alertSvc.applyAlertsStatus(alertIds, status)
+      .subscribe(message => {
+          markAlertsStatus$.unsubscribe();
+          if (message.code === 0) {
+            this.notifySvc.success("标记成功!", "");
+            this.loadAlertsTable();
+          } else {
+            this.tableLoading = false;
+            this.notifySvc.error("标记失败!", message.msg);
+          }
+        },
+        error => {
+          this.tableLoading = false;
+          markAlertsStatus$.unsubscribe();
+          this.notifySvc.error("标记失败!", error.msg)
+        }
+      );
+  }
+
   // begin: 列表多选分页逻辑
   checkedAll: boolean = false;
   onAllChecked(checked: boolean) {
@@ -126,6 +232,4 @@ export class AlertCenterComponent implements OnInit {
     this.loadAlertsTable();
   }
   // end: 列表多选分页逻辑
-
-
 }

+ 38 - 0
web-app/src/app/service/alert.service.ts

@@ -7,6 +7,8 @@ import {Alert} from "../pojo/Alert";
 
 const alerts_uri = '/alerts';
 
+const alerts_status_uri = '/alerts/status';
+
 @Injectable({
   providedIn: 'root'
 })
@@ -29,6 +31,31 @@ export class AlertService {
     return this.http.get<Message<Page<Alert>>>(alerts_uri, options);
   }
 
+  public searchAlerts(status: number | undefined, priority: number | undefined, content: string | undefined,
+                      pageIndex: number, pageSize: number) : Observable<Message<Page<Alert>>> {
+    pageIndex = pageIndex ? pageIndex : 0;
+    pageSize = pageSize ? pageSize : 8;
+    // 注意HttpParams是不可变对象 需要保存set后返回的对象为最新对象
+    let httpParams = new HttpParams();
+    httpParams = httpParams.appendAll({
+      'sort': 'id',
+      'order': 'desc',
+      'pageIndex': pageIndex,
+      'pageSize': pageSize
+    });
+    if (status != undefined && status != 9) {
+      httpParams = httpParams.append('status', status);
+    }
+    if (priority != undefined && priority != 9) {
+      httpParams = httpParams.append('priority', priority);
+    }
+    if (content != undefined && content != '' && content.trim() != '') {
+      httpParams = httpParams.append('content', content.trim());
+    }
+    const options = { params: httpParams };
+    return this.http.get<Message<Page<Alert>>>(alerts_uri, options);
+  }
+
   public deleteAlerts(alertIds: Set<number>) : Observable<Message<any>> {
     let httpParams = new HttpParams();
     alertIds.forEach(alertId => {
@@ -40,4 +67,15 @@ export class AlertService {
     return this.http.delete<Message<any>>(alerts_uri, options);
   }
 
+  public applyAlertsStatus(alertIds: Set<number>, status: number) : Observable<Message<any>> {
+    let httpParams = new HttpParams();
+    alertIds.forEach(alertId => {
+      // 注意HttpParams是不可变对象 需要保存append后返回的对象为最新对象
+      // append方法可以叠加同一key, set方法会把key之前的值覆盖只留一个key-value
+      httpParams = httpParams.append('ids', alertId);
+    })
+    const options = { params: httpParams };
+    return this.http.put<Message<any>>(`${alerts_status_uri}/${status}`, null, options);
+  }
+
 }