Bladeren bron

[manager,web-app] 监控列表,新增修改监控等编码

tomsun28 4 jaren geleden
bovenliggende
commit
c91c885412

+ 96 - 0
manager/src/main/java/com/usthe/manager/controller/MonitorsController.java

@@ -0,0 +1,96 @@
+package com.usthe.manager.controller;
+
+import com.usthe.common.entity.dto.Message;
+import com.usthe.manager.pojo.entity.Monitor;
+import com.usthe.manager.service.MonitorService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.Predicate;
+
+import java.util.HashSet;
+import java.util.List;
+
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+
+/**
+ * 监控管理批量API
+ * @author tom
+ * @date 2021/12/1 20:43
+ */
+@Api(tags = "监控列表API")
+@RestController
+@RequestMapping(path = "/monitors", produces = {APPLICATION_JSON_VALUE})
+public class MonitorsController {
+
+    @Autowired
+    private MonitorService monitorService;
+
+    @GetMapping
+    @ApiOperation(value = "查询监控列表", notes = "根据查询过滤项获取监控信息列表")
+    public ResponseEntity<Message<Page<Monitor>>> getMonitors(
+            @ApiParam(value = "监控ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
+            @ApiParam(value = "监控类型", example = "linux") @RequestParam(required = false) String app,
+            @ApiParam(value = "监控名称,模糊查询", example = "linux-127.0.0.1") @RequestParam(required = false) String name,
+            @ApiParam(value = "监控Host,模糊查询", example = "127.0.0.1") @RequestParam(required = false) String host,
+            @ApiParam(value = "排序字段,默认id", example = "name") @RequestParam(defaultValue = "id") String sort,
+            @ApiParam(value = "排序方式,asc:升序,desc:降序", example = "asc") @RequestParam(defaultValue = "asc") String order,
+            @ApiParam(value = "列表当前分页", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
+            @ApiParam(value = "列表分页数量", example = "10") @RequestParam(defaultValue = "8") int pageSize) {
+
+        Specification<Monitor> specification = (root, query, criteriaBuilder) -> {
+            Predicate predicate = criteriaBuilder.conjunction();
+            if (ids != null && !ids.isEmpty()) {
+                CriteriaBuilder.In<Long> inPredicate= criteriaBuilder.in(root.get("id"));
+                for (long id : ids) {
+                    inPredicate.value(id);
+                }
+                predicate = criteriaBuilder.and(inPredicate);
+            }
+            if (app != null && !"".equals(app)) {
+                Predicate predicateApp = criteriaBuilder.equal(root.get("app"), app);
+                predicate = criteriaBuilder.and(predicateApp);
+            }
+            if (name != null && !"".equals(name)) {
+                Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + name + "%");
+                predicate = criteriaBuilder.and(predicateName);
+            }
+            if (host != null && !"".equals(host)) {
+                Predicate predicateHost = criteriaBuilder.like(root.get("host"), "%" + host + "%");
+                predicate = criteriaBuilder.and(predicateHost);
+            }
+            return predicate;
+        };
+        // 分页是必须的
+        Sort sortExp = Sort.by(new Sort.Order(Sort.Direction.fromString(order), sort));
+        PageRequest pageRequest = PageRequest.of(pageIndex, pageSize, sortExp);
+        Page<Monitor> monitorPage = monitorService.getMonitors(specification, pageRequest);
+        Message<Page<Monitor>> message = new Message<>(monitorPage);
+        return ResponseEntity.ok(message);
+    }
+
+    @DeleteMapping
+    @ApiOperation(value = "批量删除监控", notes = "根据监控ID列表批量删除监控项")
+    public ResponseEntity<Message<Void>> deleteMonitors(
+            @ApiParam(value = "监控IDs", example = "6565463543") @RequestParam(required = false) List<Long> ids
+    ) {
+        if (ids != null && !ids.isEmpty()) {
+            monitorService.deleteMonitors(new HashSet<>(ids));
+        }
+        Message<Void> message = new Message<>();
+        return ResponseEntity.ok(message);
+    }
+}

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

@@ -2,13 +2,30 @@ package com.usthe.manager.dao;
 
 import com.usthe.manager.pojo.entity.Monitor;
 import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+import java.util.Set;
 
 /**
  * AuthResources 数据库操作
  * @author tomsun28
  * @date 2021/11/14 11:24
  */
-public interface MonitorDao extends JpaRepository<Monitor, Long> {
+public interface MonitorDao extends JpaRepository<Monitor, Long>, JpaSpecificationExecutor<Monitor> {
+
+
+    /**
+     * 根据监控ID列表删除监控
+     * @param monitorIds 监控ID列表
+     */
+    void deleteAllByIdIn(Set<Long> monitorIds);
 
+    /**
+ * 根据监控ID列表查询监控
+     * @param monitorIds 监控ID列表
+     * @return 监控列表
+     */
+    List<Monitor> findMonitorsByIdIn(Set<Long> monitorIds);
 
 }

+ 19 - 0
manager/src/main/java/com/usthe/manager/service/MonitorService.java

@@ -4,8 +4,12 @@ import com.usthe.manager.pojo.dto.MonitorDto;
 import com.usthe.manager.pojo.entity.Monitor;
 import com.usthe.manager.pojo.entity.Param;
 import com.usthe.manager.support.exception.MonitorDetectException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * 监控管理服务
@@ -55,10 +59,25 @@ public interface MonitorService {
     void deleteMonitor(long id) throws RuntimeException;
 
     /**
+     * 批量删除监控
+     * @param ids 监控ID
+     * @throws RuntimeException 删除过程中异常抛出
+     */
+    void deleteMonitors(Set<Long> ids) throws RuntimeException;
+
+    /**
      * 获取监控信息
      * @param id 监控ID
      * @return MonitorDto
      * @throws RuntimeException 查询过程中异常抛出
      */
     MonitorDto getMonitor(long id) throws RuntimeException;
+
+    /**
+     * 动态条件查询
+     * @param specification 查询条件
+     * @param pageRequest 分页参数
+     * @return 查询结果
+     */
+    Page<Monitor> getMonitors(Specification<Monitor> specification, PageRequest pageRequest);
 }

+ 36 - 4
manager/src/main/java/com/usthe/manager/service/impl/MonitorServiceImpl.java

@@ -21,12 +21,16 @@ import com.usthe.manager.support.exception.MonitorDetectException;
 import com.usthe.scheduler.JobScheduling;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -166,10 +170,20 @@ public class MonitorServiceImpl implements MonitorService {
                             break;
                         case "password":
                             // 明文密码需加密传输存储
-                            String value = param.getValue();
-                            if (!AesUtil.isCiphertext(value)) {
-                                value = AesUtil.aesEncode(value);
-                                param.setValue(value);
+                            String passwordValue = param.getValue();
+                            if (!AesUtil.isCiphertext(passwordValue)) {
+                                passwordValue = AesUtil.aesEncode(passwordValue);
+                                param.setValue(passwordValue);
+                            }
+                            break;
+                        case "boolean":
+                            // boolean校验
+                            String booleanValue = param.getValue();
+                            try {
+                                Boolean.parseBoolean(booleanValue);
+                            } catch (Exception e) {
+                                throw new IllegalArgumentException("Params field " + field + " value "
+                                        + booleanValue + " is invalid boolean value.");
                             }
                             break;
                         // todo 更多参数定义与实际值格式校验
@@ -237,6 +251,19 @@ public class MonitorServiceImpl implements MonitorService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteMonitors(Set<Long> ids) throws RuntimeException {
+        List<Monitor> monitors = monitorDao.findMonitorsByIdIn(ids);
+        if (monitors != null) {
+            monitorDao.deleteAll(monitors);
+            paramDao.deleteParamsByMonitorIdIn(ids);
+            for (Monitor monitor : monitors) {
+                jobScheduling.cancelAsyncCollectJob(monitor.getJobId());
+            }
+        }
+    }
+
+    @Override
     @Transactional(readOnly = true)
     public MonitorDto getMonitor(long id) throws RuntimeException {
         Optional<Monitor> monitorOptional = monitorDao.findById(id);
@@ -250,4 +277,9 @@ public class MonitorServiceImpl implements MonitorService {
             return null;
         }
     }
+
+    @Override
+    public Page<Monitor> getMonitors(Specification<Monitor> specification, PageRequest pageRequest) {
+        return monitorDao.findAll(specification, pageRequest);
+    }
 }

+ 4 - 4
manager/src/main/java/com/usthe/manager/support/GlobalExceptionHandler.java

@@ -129,7 +129,7 @@ public class GlobalExceptionHandler {
         if (log.isDebugEnabled()) {
             log.debug("[input argument not valid happen]-{}", errorMessage, e);
         }
-        Message<Void> message = Message.<Void>builder().msg(errorMessage.toString()).build();
+        Message<Void> message = Message.<Void>builder().msg(errorMessage.toString()).code(PARAM_INVALID).build();
         return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message);
     }
 
@@ -146,7 +146,7 @@ public class GlobalExceptionHandler {
             errorMessage = exception.getMessage();
         }
         log.warn("[scheduler warning]-{}", errorMessage);
-        Message<Void> message = Message.<Void>builder().msg(errorMessage).build();
+        Message<Void> message = Message.<Void>builder().msg(errorMessage).code(MONITOR_CONFLICT).build();
         return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
     }
 
@@ -163,7 +163,7 @@ public class GlobalExceptionHandler {
             errorMessage = exception.getMessage();
         }
         log.warn("[database error happen]-{}", errorMessage, exception);
-        Message<Void> message = Message.<Void>builder().msg(errorMessage).build();
+        Message<Void> message = Message.<Void>builder().msg(errorMessage).code(MONITOR_CONFLICT).build();
         return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
     }
 
@@ -197,7 +197,7 @@ public class GlobalExceptionHandler {
             errorMessage = exception.getMessage();
         }
         log.error("[monitor]-[unknown error happen]-{}", errorMessage, exception);
-        Message<Void> message = Message.<Void>builder().msg(errorMessage).build();
+        Message<Void> message = Message.<Void>builder().msg(errorMessage).code(MONITOR_CONFLICT).build();
         return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
     }
 

+ 1 - 1
manager/src/main/resources/define/param/A-example.yml

@@ -32,4 +32,4 @@ param:
     required: false
     # 当type为boolean时,前端用switch展示开关
     # 当type为radio单选框,checkbox复选框时,option表示可选项值列表
-    option: Yes,No
+    # option: Yes,No

+ 32 - 18
web-app/src/app/core/interceptor/default.interceptor.ts

@@ -4,7 +4,7 @@ import {
   HttpHandler,
   HttpHeaders,
   HttpInterceptor,
-  HttpRequest,
+  HttpRequest, HttpResponse,
   HttpResponseBase
 } from '@angular/common/http';
 import { Injectable, Injector } from '@angular/core';
@@ -27,6 +27,7 @@ const CODE_MESSAGE: { [key: number]: string } = {
   403: '用户得到授权,但是访问是被禁止的。',
   404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
   406: '请求的格式不可得。',
+  409: '请求与服务器端目标资源的当前状态相冲突',
   410: '请求的资源被永久删除,且不会再得到的。',
   422: '当创建一个对象时,发生一个验证错误。',
   500: '服务器发生错误,请检查服务器。',
@@ -63,9 +64,9 @@ export class DefaultInterceptor implements HttpInterceptor {
   }
 
   private checkStatus(ev: HttpResponseBase): void {
-    if (ev.status >= 200 && ev.status < 500) {
-      return;
-    }
+    // if (ev.status >= 200 && ev.status < 500) {
+    //   return;
+    // }
     const errorText = CODE_MESSAGE[ev.status] || ev.statusText;
     this.notification.error(`抱歉服务器繁忙 ${ev.status}: ${ev.url}`, errorText);
   }
@@ -158,27 +159,40 @@ export class DefaultInterceptor implements HttpInterceptor {
     const newReq = req.clone({ url, setHeaders: this.fillHeaders(req.headers) });
     return next.handle(newReq).pipe(
       mergeMap(httpEvent => {
+
         if (httpEvent instanceof HttpResponseBase) {
-          // 处理token过期自动刷新
-          switch (httpEvent.status) {
-            case 401:
-              if (this.refreshTokenEnabled) {
-                return this.tryRefreshToken(httpEvent, req, next);
-              }
-              this.toLogin();
-              break;
-            case 403 | 404 | 500:
-              this.goTo(`/exception/${httpEvent.status}?url=${req.urlWithParams}`);
-              break;
-            default:
-              break;
-          }
+          // todo 处理成功状态响应
+
           return of(httpEvent);
         } else {
           return of(httpEvent);
         }
       }),
       catchError((err: HttpErrorResponse) => {
+        // 处理失败响应,处理token过期自动刷新
+        switch (err.status) {
+          case 401:
+            if (this.refreshTokenEnabled) {
+              return this.tryRefreshToken(err, req, next);
+            }
+            this.toLogin();
+            break;
+          case 403:
+          case 404:
+          case 500:
+            this.goTo(`/exception/${err.status}?url=${req.urlWithParams}`);
+            break;
+          case 400:
+            let resp = new HttpResponse({
+              body: err.error,
+              headers: err.headers,
+              status: err.status,
+              statusText: err.statusText
+            });
+            return of(resp);
+          default:
+            break;
+        }
         this.checkStatus(err);
         console.warn(`${err.status} == ${err.message}`)
         return throwError(err);

+ 2 - 2
web-app/src/app/pojo/Message.ts

@@ -1,5 +1,5 @@
-export class Message {
-  data: any;
+export class Message<T> {
+  data!: T;
   msg!: string;
   code: number = 0;
 }

+ 1 - 1
web-app/src/app/pojo/Monitor.ts

@@ -3,7 +3,7 @@ export class Monitor {
   name!: string;
   app!: string;
   host!: string;
-  intervals!: number;
+  intervals: number = 600;
   status!: number;
   description!: string;
   creator!: string;

+ 14 - 0
web-app/src/app/pojo/Page.ts

@@ -0,0 +1,14 @@
+
+export class Page<T> {
+  content!: T[];
+  // 集合总页数
+  totalPages!: number;
+  // 集合总数
+  totalElements!: number;
+  // 查询的pageSize
+  size!: number;
+  // 查询的pageIndex,从0开始
+  number!: number;
+  // 当前页的集合数量
+  numberOfElements!: number;
+}

+ 2 - 2
web-app/src/app/pojo/Param.ts

@@ -1,6 +1,6 @@
 export class Param {
   id!: number;
-  field: string | undefined;
+  field!: string;
   type: number | undefined;
-  value: string | undefined;
+  value: any;
 }

+ 141 - 1
web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.html

@@ -1 +1,141 @@
-<p>monitor-modify works!</p>
+<nz-divider></nz-divider>
+<nz-breadcrumb>
+  <nz-breadcrumb-item>
+    <a [routerLink]="['/']">
+      <i nz-icon nzType="home"></i>
+    </a>
+  </nz-breadcrumb-item>
+  <nz-breadcrumb-item>
+    <a [routerLink]="['/monitors']">
+      <i nz-icon nzType="monitor"></i>
+      <span>监控列表</span>
+    </a>
+  </nz-breadcrumb-item>
+  <nz-breadcrumb-item>
+    <i nz-icon nzType="plus-circle"></i>
+    <span>修改 {{monitor.app}} 监控</span>
+  </nz-breadcrumb-item>
+</nz-breadcrumb>
+<nz-divider></nz-divider>
+
+<nz-spin [nzSpinning]="isSpinning">
+  <div class = "-inner-content">
+    <form nz-form>
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'host' nzRequired="true">监控Host</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <input [(ngModel)]="monitor.host" nz-input name="host" type="text" id="host">
+        </nz-form-control>
+      </nz-form-item >
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'name' nzRequired="true">监控名称</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <input [(ngModel)]="monitor.name" nz-input name="name" type="text" id="name">
+        </nz-form-control>
+      </nz-form-item >
+
+      <nz-divider></nz-divider>
+
+      <nz-form-item *ngFor="let paramDefine of paramDefines; let i = index">
+        <nz-form-label *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'" nzSpan="10">
+          <input nz-input [(ngModel)]="params[i].value" [name]="paramDefine.field" [type]="paramDefine.type" [id]="paramDefine.field">
+        </nz-form-control>
+
+        <nz-form-label *ngIf="paramDefine.type === 'password'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'password'" nzSpan="10">
+          <nz-input-group [nzSuffix]="suffixTemplate">
+            <input
+              [type]="passwordVisible ? 'text' : 'password'"
+              nz-input
+              placeholder="input password"
+              [(ngModel)]="params[i].value"
+              [id]="paramDefine.field"
+              [name]="paramDefine.field"
+            />
+          </nz-input-group>
+          <ng-template #suffixTemplate>
+            <i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
+          </ng-template>
+        </nz-form-control>
+
+
+        <nz-form-label *ngIf="paramDefine.type === 'number'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'number'" nzSpan="10">
+          <nz-input-number
+            [(ngModel)]="params[i].value"
+            [nzMin]="-1000"
+            [nzMax]="65535"
+            [nzStep]="1"
+            [nzPlaceHolder]="paramDefine.name"
+            [name]="paramDefine.field" [id]="paramDefine.field"
+          ></nz-input-number>
+        </nz-form-control>
+
+        <nz-form-label *ngIf="paramDefine.type === 'boolean'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="10">
+          <nz-switch [(ngModel)]="params[i].value" [name]="paramDefine.field" [id]="paramDefine.field"></nz-switch>
+        </nz-form-control>
+
+      </nz-form-item >
+
+      <nz-divider></nz-divider>
+
+      <nz-form-item>
+        <nz-form-label nzSpan="7" nzFor= "intervals">采集间隔</nz-form-label>
+        <nz-form-control nzSpan="10">
+          <nz-input-number [(ngModel)]="monitor.intervals" [nzMin]="10" [nzMax]="10000" [nzStep]="10"
+                           name="intervals" id="intervals">
+          </nz-input-number>
+        </nz-form-control>
+      </nz-form-item >
+
+      <nz-form-item>
+        <nz-form-label nzSpan="7" nzFor= "detect">启动探测</nz-form-label>
+        <nz-form-control nzSpan="10">
+          <nz-switch [(ngModel)]="detected" name="detect" id="detect"></nz-switch>
+        </nz-form-control>
+      </nz-form-item >
+
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'description'>描述备注</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <nz-textarea-count [nzMaxCharacterCount]="100">
+            <textarea rows="3" nz-input name="description" id="description"></textarea>
+          </nz-textarea-count>
+        </nz-form-control>
+      </nz-form-item >
+
+      <div nz-row>
+        <div nz-col nzSpan="8" nzOffset="9">
+          <button nz-button nzType="primary" type="submit" (click)="onDetect()">
+            探测
+          </button>
+          <button nz-button nzType="primary" type="submit" (click)="onSubmit()">
+            确定
+          </button>
+          <button nz-button nzType="primary" type="reset" (click)="onCancel()">
+            取消
+          </button>
+        </div>
+      </div>
+    </form>
+  </div>
+</nz-spin>
+

+ 127 - 1
web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts

@@ -1,4 +1,15 @@
 import { Component, OnInit } from '@angular/core';
+import {switchMap} from "rxjs/operators";
+import {ActivatedRoute, ParamMap, Router} from "@angular/router";
+import {Param} from "../../../pojo/Param";
+import {AppDefineService} from "../../../service/app-define.service";
+import {MonitorService} from "../../../service/monitor.service";
+import {NzNotificationService} from "ng-zorro-antd/notification";
+import {ParamDefine} from "../../../pojo/ParamDefine";
+import {Monitor} from "../../../pojo/Monitor";
+import {FormGroup} from "@angular/forms";
+import {Message} from "../../../pojo/Message";
+import {throwError} from "rxjs";
 
 @Component({
   selector: 'app-monitor-modify',
@@ -8,9 +19,124 @@ import { Component, OnInit } from '@angular/core';
 })
 export class MonitorEditComponent implements OnInit {
 
-  constructor() { }
+  constructor(private appDefineSvc: AppDefineService,
+              private monitorSvc: MonitorService,
+              private route: ActivatedRoute,
+              private router: Router,
+              private notifySvc: NzNotificationService,) { }
+
+  paramDefines!: ParamDefine[];
+  params!: Param[];
+  paramValueMap = new Map<String, Param>();
+  monitor = new Monitor();
+  profileForm: FormGroup = new FormGroup({});
+  detected: boolean = true;
+  passwordVisible: boolean = false;
+  isSpinning:boolean = false
 
   ngOnInit(): void {
+    this.route.paramMap.pipe(
+      switchMap((paramMap: ParamMap) => {
+        let id = paramMap.get("monitorId");
+        this.monitor.id = Number(id);
+        // 查询监控信息
+        return this.monitorSvc.getMonitor(this.monitor.id);
+      })
+    ).pipe(switchMap((message: Message<any>) => {
+      if (message.code === 0) {
+        this.monitor = message.data.monitor;
+        if (message.data.params != null) {
+          message.data.params.forEach((item: Param) => {
+            this.paramValueMap.set(item.field, item)
+          });
+        }
+        this.params = message.data.params;
+      } else {
+        console.warn(message.msg);
+        this.notifySvc.error("查询此监控异常", message.msg);
+        return throwError("查询此监控异常");
+      }
+      return this.appDefineSvc.getAppParamsDefine(this.monitor.app);
+    })).subscribe(message => {
+      if (message.code === 0) {
+        this.paramDefines = message.data;
+        this.params = [];
+        this.paramDefines.forEach(define => {
+          let param = this.paramValueMap.get(define.field);
+          if (param === undefined) {
+            param = new Param();
+            param.type = define.type === "number" ? 0 : 1;
+            if (define.type === "boolean") {
+              param.value = false;
+            }
+            if (param.field === "host") {
+              param.value = this.monitor.host;
+            }
+          }
+          this.params.push(param);
+        })
+      } else {
+        console.warn(message.msg);
+      }
+    });
+  }
+
+  onSubmit() {
+    // todo 暂时单独设置host属性值
+    this.params.forEach(param => {
+      if (param.field === "host") {
+        param.value = this.monitor.host;
+      }
+    });
+    let addMonitor = {
+      "detected": this.detected,
+      "monitor": this.monitor,
+      "params": this.params
+    };
+    this.isSpinning = true;
+    this.monitorSvc.newMonitor(addMonitor)
+      .subscribe(message => {
+          this.isSpinning = false;
+          if (message.code === 0) {
+            this.notifySvc.success("新增监控成功", "");
+            this.router.navigateByUrl("/monitors")
+          } else {
+            this.notifySvc.error("新增监控失败", message.msg);
+          }},
+        error => {
+          this.isSpinning = false
+        }
+      )
+  }
+
+  onDetect() {
+    // todo 暂时单独设置host属性值
+    this.params.forEach(param => {
+      if (param.field === "host") {
+        param.value = this.monitor.host;
+      }
+    });
+    let detectMonitor = {
+      "detected": this.detected,
+      "monitor": this.monitor,
+      "params": this.params
+    };
+    this.isSpinning = true;
+    this.monitorSvc.newMonitor(detectMonitor)
+      .subscribe(message => {
+        this.isSpinning = false;
+        if (message.code === 0) {
+          this.notifySvc.success("探测成功", "");
+        } else {
+          this.notifySvc.error("探测失败", message.msg);
+        }
+      })
+  }
+
+  onCancel() {
+    let app = this.monitor.app;
+    app = app ? app : '';
+    this.router.navigateByUrl(`/monitors?app=${app}`)
   }
 
 }

+ 85 - 2
web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html

@@ -1,2 +1,85 @@
-<nz-breadcrumb [nzAutoGenerate]="true"></nz-breadcrumb>
-<p>monitor-list works!</p>
+<nz-divider></nz-divider>
+<nz-breadcrumb>
+  <nz-breadcrumb-item>
+    <a [routerLink]="['/']">
+      <i nz-icon nzType="home"></i>
+    </a>
+  </nz-breadcrumb-item>
+  <nz-breadcrumb-item>
+    <i nz-icon nzType="monitor"></i>
+    <span>监控列表 {{app?app.toUpperCase() : ""}}</span>
+  </nz-breadcrumb-item>
+</nz-breadcrumb>
+<nz-divider></nz-divider>
+
+<button nz-button nzType="primary">
+  <a routerLink="/monitors/new" [queryParams]="{app: app}">
+    <i nz-icon nzType="appstore-add" nzTheme="outline"></i>
+    新增 {{app}}
+  </a>
+</button>
+<button nz-button nzType="primary" (click)="onEditMonitor()" >
+  <i nz-icon nzType="edit" nzTheme="outline"></i>
+  编辑
+</button>
+<button nz-button nzType="primary" (click)="onDeleteMonitors()">
+  <i nz-icon nzType="delete" nzTheme="outline"></i>
+  删除
+</button>
+<button nz-button nzType="primary">
+  <i nz-icon nzType="up-circle" nzTheme="outline"></i>
+  启动纳管
+</button>
+<button nz-button nzType="primary">
+  <i nz-icon nzType="down-circle" nzTheme="outline"></i>
+  取消纳管
+</button>
+
+<nz-table #fixedTable [nzData]="monitors"
+          [nzPageIndex]="pageIndex" [nzPageSize]="pageSize"
+          [nzLoading] = "tableLoading"
+          nzShowSizeChanger
+          [nzTotal]="pageTotal"
+          [nzShowTotal]="rangeTemplate"
+          [nzPageSizeOptions]="[8,15,25]"
+          nzShowPagination = "true" [nzScroll]="{ x: '1150px', y: '1240px' }">
+  <thead>
+  <tr>
+    <th nzAlign="center" nzLeft nzWidth="50px" [nzChecked]="checkedAll" (nzCheckedChange)="onAllChecked($event)"></th>
+    <th nzAlign="center">监控名称</th>
+    <th nzAlign="center">监控状态</th>
+    <th nzAlign="center">监控主机Host</th>
+    <th nzAlign="center">监控类型</th>
+    <th nzAlign="center">最新修改时间</th>
+    <th nzAlign="center" nzRight>操作</th>
+  </tr>
+  </thead>
+  <tbody>
+  <tr *ngFor="let data of fixedTable.data">
+    <td nzAlign="center" nzLeft [nzChecked]="checkedMonitorIds.has(data.id)" (nzCheckedChange)="onItemChecked(data.id, $event)"></td>
+    <td nzAlign="center">{{ data.name }}</td>
+    <td nzAlign="center">{{ data.status }}</td>
+    <td nzAlign="center">{{ data.host }}</td>
+    <td nzAlign="center">{{ data.app }}</td>
+    <td nzAlign="center">{{ data.gmtUpdate? data.gmtUpdate : data.gmtCreate }}</td>
+    <td nzAlign="center" nzRight>
+      <button nz-button nzType="primary" (click)="onEditOneMonitor(data.id)">
+        <i nz-icon nzType="edit" nzTheme="outline"></i>
+      </button>
+      <button nz-button nzType="primary"(click)="onDeleteOneMonitor(data.id)">
+        <i nz-icon nzType="delete" nzTheme="outline"></i>
+      </button>
+      <button nz-button nzType="primary">
+        <i nz-icon nzType="up-circle" nzTheme="outline"></i>
+      </button>
+      <button nz-button nzType="primary">
+        <i nz-icon nzType="down-circle" nzTheme="outline"></i>
+      </button>
+    </td>
+  </tr>
+  </tbody>
+</nz-table>
+
+<ng-template #rangeTemplate>
+  总量 {{ pageTotal }}
+</ng-template>

+ 141 - 1
web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts

@@ -1,4 +1,10 @@
 import { Component, OnInit } from '@angular/core';
+import {ActivatedRoute, Router} from "@angular/router";
+import {MonitorService} from "../../../service/monitor.service";
+import {Monitor} from "../../../pojo/Monitor";
+import {Page} from "../../../pojo/Page";
+import {NzModalService} from "ng-zorro-antd/modal";
+import {NzNotificationService} from "ng-zorro-antd/notification";
 
 @Component({
   selector: 'app-monitor-list',
@@ -8,9 +14,143 @@ import { Component, OnInit } from '@angular/core';
 })
 export class MonitorListComponent implements OnInit {
 
-  constructor() { }
+  constructor(private route: ActivatedRoute,
+              private router: Router,
+              private modal: NzModalService,
+              private notifySvc: NzNotificationService,
+              private monitorSvc: MonitorService) { }
+
+  app!: string;
+  pageIndex: number = 1;
+  pageSize: number = 8;
+  pageTotal: number = 0;
+  monitors!: Monitor[];
+  pageMonitors!: Page<Monitor>;
+  tableLoading: boolean = true;
+  checkedMonitorIds = new Set<number>();
 
   ngOnInit(): void {
+    this.route.queryParamMap
+      .subscribe(paramMap => {
+        this.app = paramMap.get("app") || '';
+        this.initMonitorTable();
+      });
+  }
+
+  initMonitorTable() {
+    let monitorInit$ = this.monitorSvc.getMonitors(this.app, this.pageIndex - 1, this.pageSize)
+      .subscribe(message => {
+        this.tableLoading = false;
+        if (message.code === 0) {
+          this.pageMonitors = message.data;
+          this.monitors = this.pageMonitors.content;
+          this.pageIndex = this.pageMonitors.number + 1;
+          this.pageTotal = this.pageMonitors.totalElements;
+        } else {
+          console.warn(message.msg);
+        }
+        monitorInit$.unsubscribe();
+      },
+      error => {
+        this.tableLoading = false;
+        monitorInit$.unsubscribe();
+      });
+  }
+
+  onEditOneMonitor(monitorId: number) {
+    if (monitorId == null) {
+      this.notifySvc.warning("未选中任何待编辑项!","");
+      return;
+    }
+    this.router.navigateByUrl(`/monitors/${monitorId}/edit`);
+    // 参数样例
+    // this.router.navigate(['/monitors/new'],{queryParams: {app: "linux"}});
   }
 
+  onEditMonitor() {
+    // 编辑时只能选中一个监控
+    if (this.checkedMonitorIds == null || this.checkedMonitorIds.size === 0) {
+      this.notifySvc.warning("未选中任何待编辑项!","");
+      return;
+    }
+    if (this.checkedMonitorIds.size > 1) {
+      this.notifySvc.warning("只能对一个选中项进行编辑!","");
+      return;
+    }
+    let monitorId = 0;
+    this.checkedMonitorIds.forEach(item => monitorId = item);
+    this.router.navigateByUrl(`/monitors/${monitorId}/edit`);
+  }
+
+  onDeleteOneMonitor(monitorId: number) {
+    let monitors = new Set<number>();
+    monitors.add(monitorId);
+    this.modal.confirm({
+      nzTitle: '请确认是否删除!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.deleteMonitors(monitors)
+    });
+  }
+
+  onDeleteMonitors() {
+    if (this.checkedMonitorIds == null || this.checkedMonitorIds.size === 0) {
+      this.notifySvc.warning("未选中任何待删除项!","");
+      return;
+    }
+    this.modal.confirm({
+      nzTitle: '请确认是否批量删除!',
+      nzOkText: '确定',
+      nzCancelText: '取消',
+      nzOkDanger: true,
+      nzOkType: "primary",
+      nzOnOk: () => this.deleteMonitors(this.checkedMonitorIds)
+    });
+  }
+
+
+  deleteMonitors(monitors: Set<number>) {
+    if (monitors == null || monitors.size == 0) {
+      this.notifySvc.warning("未选中任何待删除项!","");
+      return;
+    }
+    const deleteMonitors$ = this.monitorSvc.deleteMonitors(monitors)
+      .subscribe(message => {
+          deleteMonitors$.unsubscribe();
+        if (message.code === 0) {
+          this.notifySvc.success("删除成功!", "");
+          this.initMonitorTable();
+        } else {
+          this.notifySvc.error("删除失败!", message.msg);
+        }
+    },
+        error => {
+          deleteMonitors$.unsubscribe();
+          this.notifySvc.error("删除失败!", error.msg)
+        }
+    );
+  }
+
+
+  // begin: 列表多选逻辑
+  checkedAll: boolean = false;
+  onAllChecked(checked: boolean) {
+    if (checked) {
+      this.monitors.forEach(monitor => this.checkedMonitorIds.add(monitor.id));
+    } else {
+      this.checkedMonitorIds.clear();
+    }
+  }
+  onItemChecked(monitorId: number, checked: boolean) {
+    if (checked) {
+      this.checkedMonitorIds.add(monitorId);
+    } else {
+      this.checkedMonitorIds.delete(monitorId);
+    }
+  }
+  // end: 列表多选逻辑
+
+
 }

+ 108 - 103
web-app/src/app/routes/monitor/monitor-new/monitor-new.component.html

@@ -18,119 +18,124 @@
 </nz-breadcrumb>
 <nz-divider></nz-divider>
 
-<div class = "-inner-content">
-  <form nz-form (ngSubmit)="onSubmit()">
-    <nz-form-item>
-      <nz-form-label [nzSpan]="7" nzFor= 'host' nzRequired="true">监控Host</nz-form-label>
-      <nz-form-control [nzSpan]="10">
-        <input [(ngModel)]="monitor.host" nz-input name="host" type="text" id="host">
-      </nz-form-control>
-    </nz-form-item >
-    <nz-form-item>
-      <nz-form-label [nzSpan]="7" nzFor= 'name' nzRequired="true">监控名称</nz-form-label>
-      <nz-form-control [nzSpan]="10">
-        <input [(ngModel)]="monitor.name" nz-input name="name" type="text" id="name">
-      </nz-form-control>
-    </nz-form-item >
+<nz-spin [nzSpinning]="isSpinning">
+  <div class = "-inner-content">
+    <form nz-form>
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'host' nzRequired="true">监控Host</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <input [(ngModel)]="monitor.host" nz-input name="host" type="text" id="host">
+        </nz-form-control>
+      </nz-form-item >
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'name' nzRequired="true">监控名称</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <input [(ngModel)]="monitor.name" nz-input name="name" type="text" id="name">
+        </nz-form-control>
+      </nz-form-item >
 
-    <nz-divider></nz-divider>
+      <nz-divider></nz-divider>
 
-    <nz-form-item *ngFor="let paramDefine of paramDefines; let i = index">
-      <nz-form-label *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'"
-                     nzSpan="7"
-                     [nzRequired]="paramDefine.required"
-                     [nzFor]= "paramDefine.field">{{paramDefine.name}}
-      </nz-form-label>
-      <nz-form-control *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'" nzSpan="10">
-        <input nz-input [(ngModel)]="params[i].value" [name]="paramDefine.field" [type]="paramDefine.type" [id]="paramDefine.field">
-      </nz-form-control>
+      <nz-form-item *ngFor="let paramDefine of paramDefines; let i = index">
+        <nz-form-label *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.field !== 'host' && paramDefine.type ==='text'" nzSpan="10">
+          <input nz-input [(ngModel)]="params[i].value" [name]="paramDefine.field" [type]="paramDefine.type" [id]="paramDefine.field">
+        </nz-form-control>
 
-      <nz-form-label *ngIf="paramDefine.type === 'password'"
-                     nzSpan="7"
-                     [nzRequired]="paramDefine.required"
-                     [nzFor]= "paramDefine.field">{{paramDefine.name}}
-      </nz-form-label>
-      <nz-form-control *ngIf="paramDefine.type === 'password'" nzSpan="10">
-        <nz-input-group [nzSuffix]="suffixTemplate">
-          <input
-            [type]="passwordVisible ? 'text' : 'password'"
-            nz-input
-            placeholder="input password"
-            [(ngModel)]="params[i].value"
-            [id]="paramDefine.field"
-            [name]="paramDefine.field"
-          />
-        </nz-input-group>
-        <ng-template #suffixTemplate>
-          <i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
-        </ng-template>
-      </nz-form-control>
+        <nz-form-label *ngIf="paramDefine.type === 'password'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'password'" nzSpan="10">
+          <nz-input-group [nzSuffix]="suffixTemplate">
+            <input
+              [type]="passwordVisible ? 'text' : 'password'"
+              nz-input
+              placeholder="input password"
+              [(ngModel)]="params[i].value"
+              [id]="paramDefine.field"
+              [name]="paramDefine.field"
+            />
+          </nz-input-group>
+          <ng-template #suffixTemplate>
+            <i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
+          </ng-template>
+        </nz-form-control>
 
 
-      <nz-form-label *ngIf="paramDefine.type === 'number'"
-                     nzSpan="7"
-                     [nzRequired]="paramDefine.required"
-                     [nzFor]= "paramDefine.field">{{paramDefine.name}}
-      </nz-form-label>
-      <nz-form-control *ngIf="paramDefine.type === 'number'" nzSpan="10">
-        <nz-input-number
-          [(ngModel)]="params[i].value"
-          [nzMin]="-1000"
-          [nzMax]="65535"
-          [nzStep]="1"
-          [nzPlaceHolder]="paramDefine.name"
-          [name]="paramDefine.field" [id]="paramDefine.field"
-        ></nz-input-number>
-      </nz-form-control>
+        <nz-form-label *ngIf="paramDefine.type === 'number'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'number'" nzSpan="10">
+          <nz-input-number
+            [(ngModel)]="params[i].value"
+            [nzMin]="-1000"
+            [nzMax]="65535"
+            [nzStep]="1"
+            [nzPlaceHolder]="paramDefine.name"
+            [name]="paramDefine.field" [id]="paramDefine.field"
+          ></nz-input-number>
+        </nz-form-control>
 
-      <nz-form-label *ngIf="paramDefine.type === 'boolean'"
-                     nzSpan="7"
-                     [nzRequired]="paramDefine.required"
-                     [nzFor]= "paramDefine.field">{{paramDefine.name}}
-      </nz-form-label>
-      <nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="10">
-        <nz-switch [(ngModel)]="params[i].value" [name]="paramDefine.field" [id]="paramDefine.field"></nz-switch>
-      </nz-form-control>
+        <nz-form-label *ngIf="paramDefine.type === 'boolean'"
+                       nzSpan="7"
+                       [nzRequired]="paramDefine.required"
+                       [nzFor]= "paramDefine.field">{{paramDefine.name}}
+        </nz-form-label>
+        <nz-form-control *ngIf="paramDefine.type === 'boolean'" nzSpan="10">
+          <nz-switch [(ngModel)]="params[i].value" [name]="paramDefine.field" [id]="paramDefine.field"></nz-switch>
+        </nz-form-control>
 
-    </nz-form-item >
+      </nz-form-item >
 
-    <nz-divider></nz-divider>
+      <nz-divider></nz-divider>
 
-    <nz-form-item>
-      <nz-form-label nzSpan="7" nzFor= "intervals">采集间隔</nz-form-label>
-      <nz-form-control nzSpan="10">
-        <nz-input-number [(ngModel)]="monitor.intervals" [nzMin]="10" [nzMax]="10000" [nzStep]="10" id="intervals"></nz-input-number>
-      </nz-form-control>
-    </nz-form-item >
+      <nz-form-item>
+        <nz-form-label nzSpan="7" nzFor= "intervals">采集间隔</nz-form-label>
+        <nz-form-control nzSpan="10">
+          <nz-input-number [(ngModel)]="monitor.intervals" [nzMin]="10" [nzMax]="10000" [nzStep]="10"
+                           name="intervals" id="intervals">
+          </nz-input-number>
+        </nz-form-control>
+      </nz-form-item >
 
-    <nz-form-item>
-      <nz-form-label nzSpan="7" nzFor= "detect">启动探测</nz-form-label>
-      <nz-form-control nzSpan="10">
-        <nz-switch [(ngModel)]="detected" name="detect" id="detect"></nz-switch>
-      </nz-form-control>
-    </nz-form-item >
+      <nz-form-item>
+        <nz-form-label nzSpan="7" nzFor= "detect">启动探测</nz-form-label>
+        <nz-form-control nzSpan="10">
+          <nz-switch [(ngModel)]="detected" name="detect" id="detect"></nz-switch>
+        </nz-form-control>
+      </nz-form-item >
 
-    <nz-form-item>
-      <nz-form-label [nzSpan]="7" nzFor= 'description'>描述备注</nz-form-label>
-      <nz-form-control [nzSpan]="10">
-        <nz-textarea-count [nzMaxCharacterCount]="100">
-          <textarea rows="3" nz-input name="description" id="description"></textarea>
-        </nz-textarea-count>
-      </nz-form-control>
-    </nz-form-item >
+      <nz-form-item>
+        <nz-form-label [nzSpan]="7" nzFor= 'description'>描述备注</nz-form-label>
+        <nz-form-control [nzSpan]="10">
+          <nz-textarea-count [nzMaxCharacterCount]="100">
+            <textarea rows="3" nz-input name="description" id="description"></textarea>
+          </nz-textarea-count>
+        </nz-form-control>
+      </nz-form-item >
 
-    <div nz-row>
-      <div nz-col nzSpan="8" nzOffset="9">
-        <button nz-button nzType="primary" type="submit">
-          探测
-        </button>
-        <button nz-button nzType="primary" type="submit">
-          确定
-        </button>
-        <button nz-button nzType="primary" nzDanger="true" type="reset">
-          取消
-        </button>
+      <div nz-row>
+        <div nz-col nzSpan="8" nzOffset="9">
+          <button nz-button nzType="primary" type="submit" (click)="onDetect()">
+            探测
+          </button>
+          <button nz-button nzType="primary" type="submit" (click)="onSubmit()">
+            确定
+          </button>
+          <button nz-button nzType="primary" type="reset" (click)="onCancel()">
+            取消
+          </button>
+        </div>
       </div>
-    </div>
-  </form>
-</div>
+    </form>
+  </div>
+</nz-spin>
+

+ 47 - 2
web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts

@@ -23,7 +23,8 @@ export class MonitorNewComponent implements OnInit {
   monitor!: Monitor;
   profileForm: FormGroup = new FormGroup({});
   detected: boolean = true;
-  passwordVisible!: boolean;
+  passwordVisible: boolean = false;
+  isSpinning:boolean = false
   constructor(private appDefineSvc: AppDefineService,
               private monitorSvc: MonitorService,
               private route: ActivatedRoute,
@@ -48,6 +49,9 @@ export class MonitorNewComponent implements OnInit {
           let param = new Param();
           param.field = define.field;
           param.type = define.type === "number" ? 0 : 1;
+          if (define.type === "boolean") {
+            param.value = false;
+          }
           this.params.push(param);
         })
       } else {
@@ -58,20 +62,61 @@ export class MonitorNewComponent implements OnInit {
   }
 
   onSubmit() {
+    // todo 暂时单独设置host属性值
+    this.params.forEach(param => {
+      if (param.field === "host") {
+        param.value = this.monitor.host;
+      }
+    });
     let addMonitor = {
       "detected": this.detected,
       "monitor": this.monitor,
       "params": this.params
     };
+    this.isSpinning = true;
     this.monitorSvc.newMonitor(addMonitor)
       .subscribe(message => {
+        this.isSpinning = false;
         if (message.code === 0) {
           this.notifySvc.success("新增监控成功", "");
           this.router.navigateByUrl("/monitors")
         } else {
           this.notifySvc.error("新增监控失败", message.msg);
+        }},
+        error => {
+          this.isSpinning = false
         }
-    })
+      )
+  }
+
+  onDetect() {
+    // todo 暂时单独设置host属性值
+    this.params.forEach(param => {
+      if (param.field === "host") {
+        param.value = this.monitor.host;
+      }
+    });
+    let detectMonitor = {
+      "detected": this.detected,
+      "monitor": this.monitor,
+      "params": this.params
+    };
+    this.isSpinning = true;
+    this.monitorSvc.newMonitor(detectMonitor)
+      .subscribe(message => {
+        this.isSpinning = false;
+        if (message.code === 0) {
+          this.notifySvc.success("探测成功", "");
+        } else {
+          this.notifySvc.error("探测失败", message.msg);
+        }
+      })
+  }
+
+  onCancel() {
+    let app = this.monitor.app;
+    app = app ? app : '';
+    this.router.navigateByUrl(`/monitors?app=${app}`)
   }
 
 }

+ 1 - 1
web-app/src/app/routes/monitor/monitor-routing.module.ts

@@ -6,7 +6,7 @@ import {MonitorEditComponent} from "./monitor-edit/monitor-edit.component";
 import {MonitorDetailComponent} from "./monitor-detail/monitor-detail.component";
 
 const routes: Routes = [
-  { path: '', component: MonitorNewComponent },
+  { path: '', component: MonitorListComponent },
   { path: 'new', component: MonitorNewComponent },
   { path: ':monitorId/edit', component: MonitorEditComponent },
   { path: ':monitorId', component: MonitorDetailComponent },

+ 3 - 2
web-app/src/app/service/app-define.service.ts

@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import {Message} from "../pojo/Message";
 import {Observable} from "rxjs";
+import {ParamDefine} from "../pojo/ParamDefine";
 
 
 @Injectable({
@@ -11,12 +12,12 @@ export class AppDefineService {
 
   constructor(private http : HttpClient) { }
 
-  public getAppParamsDefine(app: string | undefined | null) : Observable<Message> {
+  public getAppParamsDefine(app: string | undefined | null) : Observable<Message<ParamDefine[]>> {
     if (app === null || app === undefined) {
       console.log("getAppParamsDefine app can not null");
     }
     const paramDefineUri = `/apps/${app}/params`;
-    return this.http.get<Message>(paramDefineUri);
+    return this.http.get<Message<ParamDefine[]>>(paramDefineUri);
   }
 
 }

+ 48 - 3
web-app/src/app/service/monitor.service.ts

@@ -1,9 +1,13 @@
 import { Injectable } from '@angular/core';
-import {HttpClient} from "@angular/common/http";
+import {HttpClient, HttpParams} from "@angular/common/http";
 import {Observable} from "rxjs";
 import {Message} from "../pojo/Message";
+import {Page} from "../pojo/Page";
+import {Monitor} from "../pojo/Monitor";
 
 const monitor_uri = "/monitor";
+const monitors_uri = "/monitors";
+const detect_monitor_uri = "/monitor/detect"
 
 @Injectable({
   providedIn: 'root'
@@ -12,8 +16,49 @@ export class MonitorService {
 
   constructor(private http : HttpClient) { }
 
-  public newMonitor(body: any) : Observable<Message> {
-    return this.http.post<Message>(monitor_uri, body);
+  public newMonitor(body: any) : Observable<Message<any>> {
+    return this.http.post<Message<any>>(monitor_uri, body);
+  }
+
+  public editMonitor(body: any) : Observable<Message<any>> {
+    return this.http.put<Message<any>>(monitor_uri, body);
+  }
+
+  public deleteMonitor(monitorId: number) : Observable<Message<any>> {
+    return this.http.delete<Message<any>>(`${monitor_uri}/${monitorId}`);
+  }
+
+  public deleteMonitors(monitorIds: Set<number>) : Observable<Message<any>> {
+    let httpParams = new HttpParams();
+    monitorIds.forEach(monitorId => {
+      // 注意HttpParams是不可变对象 需要保存set后返回的对象为最新对象
+      httpParams = httpParams.set('ids', monitorId);
+    })
+    const options = { params: httpParams };
+    return this.http.delete<Message<any>>(monitors_uri, options);
+  }
+
+  public detectMonitor(body: any) : Observable<Message<any>> {
+    return this.http.post<Message<any>>(detect_monitor_uri, body);
+  }
+
+  public getMonitor(monitorId: number) : Observable<Message<any>> {
+    return this.http.get<Message<any>>(`${monitor_uri}/${monitorId}`);
+  }
+
+  public getMonitors(app: string, pageIndex: number, pageSize: number) : Observable<Message<Page<Monitor>>> {
+    app = app.trim();
+    pageIndex = pageIndex ? 1 : pageIndex;
+    pageSize = pageSize ? 10 : pageSize;
+    // 注意HttpParams是不可变对象 需要保存set后返回的对象为最新对象
+    let httpParams = new HttpParams();
+    httpParams = httpParams.appendAll({
+      'app': app,
+      'pageIndex': pageIndex,
+      'pageSize': pageSize
+    });
+    const options = { params: httpParams };
+    return this.http.get<Message<Page<Monitor>>>(monitors_uri, options);
   }
 
 }

+ 2 - 2
web-app/src/assets/tmp/app-data.json

@@ -36,12 +36,12 @@
           "children": [
             {
               "text": "http",
-              "link": "/monitors",
+              "link": "/monitors?app=http",
               "i18n": "monitor.app.http"
             },
             {
               "text": "ping",
-              "link": "/monitors",
+              "link": "/monitors?app=ping",
               "i18n": "monitor.app.ping"
             },
             {