Procházet zdrojové kódy

[web-app] http拦截器修改,新增监控页面编码

tomsun28 před 4 roky
rodič
revize
090e6e8c16
37 změnil soubory, kde provedl 663 přidání a 172 odebrání
  1. 25 24
      web-app/package.json
  2. 2 2
      web-app/proxy.conf.js
  3. 6 4
      web-app/src/app/app.module.ts
  4. 1 1
      web-app/src/app/core/i18n/i18n.service.ts
  5. 1 1
      web-app/src/app/core/index.ts
  6. 39 112
      web-app/src/app/core/interceptor/default.interceptor.ts
  7. 1 2
      web-app/src/app/core/startup/startup.service.ts
  8. 5 0
      web-app/src/app/pojo/Message.ts
  9. 13 0
      web-app/src/app/pojo/Monitor.ts
  10. 6 0
      web-app/src/app/pojo/Param.ts
  11. 9 0
      web-app/src/app/pojo/ParamDefine.ts
  12. 1 0
      web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html
  13. 25 0
      web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.spec.ts
  14. 16 0
      web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts
  15. 1 0
      web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.html
  16. 25 0
      web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.spec.ts
  17. 16 0
      web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts
  18. 2 0
      web-app/src/app/routes/monitor/monitor-list/monitor-list.component.html
  19. 25 0
      web-app/src/app/routes/monitor/monitor-list/monitor-list.component.spec.ts
  20. 16 0
      web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts
  21. 136 0
      web-app/src/app/routes/monitor/monitor-new/monitor-new.component.html
  22. 25 0
      web-app/src/app/routes/monitor/monitor-new/monitor-new.component.spec.ts
  23. 77 0
      web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts
  24. 9 0
      web-app/src/app/routes/monitor/monitor-routing.module.ts
  25. 20 5
      web-app/src/app/routes/monitor/monitor.module.ts
  26. 3 4
      web-app/src/app/routes/routes-routing.module.ts
  27. 16 0
      web-app/src/app/service/app-define.service.spec.ts
  28. 22 0
      web-app/src/app/service/app-define.service.ts
  29. 16 0
      web-app/src/app/service/local-storage.service.spec.ts
  30. 29 0
      web-app/src/app/service/local-storage.service.ts
  31. 16 0
      web-app/src/app/service/monitor.service.spec.ts
  32. 19 0
      web-app/src/app/service/monitor.service.ts
  33. 16 0
      web-app/src/app/service/my-service.service.spec.ts
  34. 9 0
      web-app/src/app/service/my-service.service.ts
  35. 10 10
      web-app/src/assets/tmp/app-data.json
  36. 1 2
      web-app/src/environments/environment.prod.ts
  37. 4 5
      web-app/src/environments/environment.ts

+ 25 - 24
web-app/package.json

@@ -22,6 +22,7 @@
   "private": true,
   "dependencies": {
     "@angular/animations": "~12.2.0",
+    "@angular/cdk": "~12.2.0",
     "@angular/common": "~12.2.0",
     "@angular/compiler": "~12.2.0",
     "@angular/core": "~12.2.0",
@@ -29,43 +30,38 @@
     "@angular/platform-browser": "~12.2.0",
     "@angular/platform-browser-dynamic": "~12.2.0",
     "@angular/router": "~12.2.0",
-    "@angular/cdk": "~12.2.0",
-    "ng-alain": "^12.4.2",
-    "ng-zorro-antd": "^12.0.2",
-    "rxjs": "~6.6.0",
-    "tslib": "^2.3.0",
-    "zone.js": "~0.11.4",
     "@delon/abc": "^12.4.2",
     "@delon/acl": "^12.4.2",
     "@delon/auth": "^12.4.2",
     "@delon/cache": "^12.4.2",
+    "@delon/chart": "^12.4.2",
     "@delon/form": "^12.4.2",
     "@delon/mock": "^12.4.2",
     "@delon/theme": "^12.4.2",
     "@delon/util": "^12.4.2",
-    "@delon/chart": "^12.4.2",
     "ajv": "^8.6.2",
     "ajv-formats": "^2.1.1",
-    "screenfull": "^5.1.0"
+    "ng-alain": "^12.4.2",
+    "ng-zorro-antd": "^12.0.2",
+    "rxjs": "~6.6.0",
+    "screenfull": "^5.1.0",
+    "tslib": "^2.3.0",
+    "zone.js": "~0.11.4"
   },
   "devDependencies": {
     "@angular-devkit/build-angular": "~12.2.13",
-    "@angular/cli": "~12.2.13",
-    "@angular/compiler-cli": "~12.2.0",
-    "@types/jasmine": "~3.8.0",
-    "@types/node": "^12.11.1",
-    "jasmine-core": "~3.8.0",
-    "karma": "~6.3.0",
-    "karma-chrome-launcher": "~3.1.0",
-    "karma-coverage": "~2.0.3",
-    "karma-jasmine": "~4.0.0",
-    "karma-jasmine-html-reporter": "~1.7.0",
-    "typescript": "~4.3.5",
     "@angular-eslint/builder": "~12.3.1",
     "@angular-eslint/eslint-plugin": "~12.3.1",
     "@angular-eslint/eslint-plugin-template": "~12.3.1",
     "@angular-eslint/schematics": "~12.3.1",
     "@angular-eslint/template-parser": "~12.3.1",
+    "@angular/cli": "~12.2.13",
+    "@angular/compiler-cli": "~12.2.0",
+    "@angular/language-service": "~12.2.0",
+    "@delon/testing": "^12.4.2",
+    "@ngx-formly/schematics": "^5.10.23",
+    "@types/jasmine": "~3.8.0",
+    "@types/node": "^12.11.1",
     "@typescript-eslint/eslint-plugin": "~4.29.2",
     "@typescript-eslint/parser": "~4.29.2",
     "eslint": "^7.32.0",
@@ -74,19 +70,24 @@
     "eslint-plugin-jsdoc": "~36.0.7",
     "eslint-plugin-prefer-arrow": "~1.2.3",
     "eslint-plugin-prettier": "^2.2.1",
-    "prettier": "^2.2.1",
+    "jasmine-core": "~3.8.0",
+    "karma": "~6.3.0",
+    "karma-chrome-launcher": "~3.1.0",
+    "karma-coverage": "~2.0.3",
+    "karma-jasmine": "~4.0.0",
+    "karma-jasmine-html-reporter": "~1.7.0",
+    "lint-staged": "^11.1.2",
     "ng-alain": "^12.4.2",
     "ng-alain-plugin-theme": "^12.0.0",
+    "prettier": "^2.2.1",
     "source-map-explorer": "^2.5.2",
-    "@angular/language-service": "~12.2.0",
-    "@delon/testing": "^12.4.2",
-    "lint-staged": "^11.1.2",
     "stylelint": "^13.13.1",
     "stylelint-config-prettier": "^8.0.2",
     "stylelint-config-rational-order": "^0.1.2",
     "stylelint-config-standard": "^22.0.0",
     "stylelint-declaration-block-no-ignored-properties": "^2.4.0",
-    "stylelint-order": "^4.1.0"
+    "stylelint-order": "^4.1.0",
+    "typescript": "~4.3.5"
   },
   "lint-staged": {
     "(src)/**/*.{html,ts}": [

+ 2 - 2
web-app/proxy.conf.js

@@ -9,8 +9,8 @@ module.exports = {
   /**
    * The following means that all requests are directed to the backend `https://localhost:9000/`
    */
-  // '/': {
-  //   target: 'https://localhost:9000/',
+  // '/apps/*': {
+  //   target: 'https://localhost:8080',
   //   secure: false, // Ignore invalid SSL certificates
   //   changeOrigin: true
   // }

+ 6 - 4
web-app/src/app/app.module.ts

@@ -51,7 +51,7 @@ import { HTTP_INTERCEPTORS } from '@angular/common/http';
 import { DefaultInterceptor } from '@core';
 import { SimpleInterceptor } from '@delon/auth';
 const INTERCEPTOR_PROVIDES = [
-  { provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true},
+  // { provide: HTTP_INTERCEPTORS, useClass: SimpleInterceptor, multi: true},
   { provide: HTTP_INTERCEPTORS, useClass: DefaultInterceptor, multi: true}
 ];
 // #endregion
@@ -65,7 +65,7 @@ import { StartupService } from '@core';
 export function StartupServiceFactory(startupService: StartupService): () => Observable<void> {
   return () => startupService.load();
 }
-const APPINIT_PROVIDES = [
+const APP_INIT_PROVIDES = [
   StartupService,
   {
     provide: APP_INITIALIZER,
@@ -83,6 +83,7 @@ import { LayoutModule } from './layout/layout.module';
 import { RoutesModule } from './routes/routes.module';
 import { SharedModule } from './shared/shared.module';
 import { STWidgetModule } from './shared/st-widget/st-widget.module';
+import { ReactiveFormsModule } from '@angular/forms';
 
 @NgModule({
   declarations: [
@@ -101,13 +102,14 @@ import { STWidgetModule } from './shared/st-widget/st-widget.module';
     NzMessageModule,
     NzNotificationModule,
     ...FORM_MODULES,
-    ...GLOBAL_THIRD_MODULES
+    ...GLOBAL_THIRD_MODULES,
+    ReactiveFormsModule
   ],
   providers: [
     ...LANG_PROVIDES,
     ...INTERCEPTOR_PROVIDES,
     ...I18NSERVICE_PROVIDES,
-    ...APPINIT_PROVIDES
+    ...APP_INIT_PROVIDES
   ],
   bootstrap: [AppComponent]
 })

+ 1 - 1
web-app/src/app/core/i18n/i18n.service.ts

@@ -92,7 +92,7 @@ export class I18NService extends AlainI18nBaseService {
   }
 
   loadLangData(lang: string): Observable<NzSafeAny> {
-    return this.http.get(`assets/tmp/i18n/${lang}.json`);
+    return this.http.get(`http://localhost:4200/assets/tmp/i18n/${lang}.json`);
   }
 
   use(lang: string, data: Record<string, unknown>): void {

+ 1 - 1
web-app/src/app/core/index.ts

@@ -1,4 +1,4 @@
 export * from './i18n/i18n.service';
 export * from './module-import-guard';
-export * from './net/default.interceptor';
+export * from './interceptor/default.interceptor';
 export * from './startup/startup.service';

+ 39 - 112
web-app/src/app/core/net/default.interceptor.ts → web-app/src/app/core/interceptor/default.interceptor.ts

@@ -15,8 +15,9 @@ import { environment } from '@env/environment';
 import { NzNotificationService } from 'ng-zorro-antd/notification';
 import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
 import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators';
+import {LocalStorageService} from "../../service/local-storage.service";
 
-const CODEMESSAGE: { [key: number]: string } = {
+const CODE_MESSAGE: { [key: number]: string } = {
   200: '服务器成功返回请求的数据。',
   201: '新建或修改数据成功。',
   202: '一个请求已经进入后台排队(异步任务)。',
@@ -40,15 +41,10 @@ const CODEMESSAGE: { [key: number]: string } = {
 @Injectable()
 export class DefaultInterceptor implements HttpInterceptor {
   private refreshTokenEnabled = environment.api.refreshTokenEnabled;
-  private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
   private refreshToking = false;
   private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
 
-  constructor(private injector: Injector) {
-    if (this.refreshTokenType === 'auth-refresh') {
-      this.buildAuthRefresh();
-    }
-  }
+  constructor(private injector: Injector, private storageSvc: LocalStorageService) { }
 
   private get notification(): NzNotificationService {
     return this.injector.get(NzNotificationService);
@@ -67,12 +63,11 @@ export class DefaultInterceptor implements HttpInterceptor {
   }
 
   private checkStatus(ev: HttpResponseBase): void {
-    if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) {
+    if (ev.status >= 200 && ev.status < 500) {
       return;
     }
-
-    const errortext = CODEMESSAGE[ev.status] || ev.statusText;
-    this.notification.error(`请求错误 ${ev.status}: ${ev.url}`, errortext);
+    const errorText = CODE_MESSAGE[ev.status] || ev.statusText;
+    this.notification.error(`抱歉服务器繁忙 ${ev.status}: ${ev.url}`, errorText);
   }
 
   /**
@@ -109,6 +104,7 @@ export class DefaultInterceptor implements HttpInterceptor {
         this.refreshToking = false;
         this.refreshToken$.next(res);
         // 重新保存新 token
+        this.storageSvc.storageAuthorizationToken(res);
         this.tokenSrv.set(res);
         // 重新发起请求
         return next.handle(this.reAttachToken(req));
@@ -124,138 +120,69 @@ export class DefaultInterceptor implements HttpInterceptor {
   /**
    * 重新附加新 Token 信息
    *
-   * > 由于已经发起的请求,不会再走一遍 `@delon/auth` 因此需要结合业务情况重新附加新的 Token
    */
   private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
-    // 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor`
-    const token = this.tokenSrv.get()?.token;
+    let token = this.storageSvc.getAuthorizationToken();
     return req.clone({
       setHeaders: {
-        token: `Bearer ${token}`
+        'Authorization': `Bearer ${token}`
       }
     });
   }
 
-  // #endregion
-
-  // #region 刷新Token方式二:使用 `@delon/auth` 的 `refresh` 接口
-
-  private buildAuthRefresh(): void {
-    if (!this.refreshTokenEnabled) {
-      return;
-    }
-    this.tokenSrv.refresh
-      .pipe(
-        filter(() => !this.refreshToking),
-        switchMap(res => {
-          console.log(res);
-          this.refreshToking = true;
-          return this.refreshTokenRequest();
-        })
-      )
-      .subscribe(
-        res => {
-          // TODO: Mock expired value
-          res.expired = +new Date() + 1000 * 60 * 5;
-          this.refreshToking = false;
-          this.tokenSrv.set(res);
-        },
-        () => this.toLogin()
-      );
-  }
-
-  // #endregion
 
   private toLogin(): void {
     this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
     this.goTo(this.tokenSrv.login_url!);
   }
 
-  private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> {
-    this.checkStatus(ev);
-    // 业务处理:一些通用操作
-    switch (ev.status) {
-      case 200:
-        // 业务层级错误处理,以下是假定restful有一套统一输出格式(指不管成功与否都有相应的数据格式)情况下进行处理
-        // 例如响应内容:
-        //  错误内容:{ status: 1, msg: '非法参数' }
-        //  正确内容:{ status: 0, response: {  } }
-        // 则以下代码片断可直接适用
-        // if (ev instanceof HttpResponse) {
-        //   const body = ev.body;
-        //   if (body && body.status !== 0) {
-        //     this.injector.get(NzMessageService).error(body.msg);
-        //     // 注意:这里如果继续抛出错误会被行254的 catchError 二次拦截,导致外部实现的 Pipe、subscribe 操作被中断,例如:this.http.get('/').subscribe() 不会触发
-        //     // 如果你希望外部实现,需要手动移除行254
-        //     return throwError({});
-        //   } else {
-        //     // 忽略 Blob 文件体
-        //     if (ev.body instanceof Blob) {
-        //        return of(ev);
-        //     }
-        //     // 重新修改 `body` 内容为 `response` 内容,对于绝大多数场景已经无须再关心业务状态码
-        //     return of(new HttpResponse(Object.assign(ev, { body: body.response })));
-        //     // 或者依然保持完整的格式
-        //     return of(ev);
-        //   }
-        // }
-        break;
-      case 401:
-        if (this.refreshTokenEnabled && this.refreshTokenType === 're-request') {
-          return this.tryRefreshToken(ev, req, next);
-        }
-        this.toLogin();
-        break;
-      case 403:
-      case 404:
-      case 500:
-        // this.goTo(`/exception/${ev.status}?url=${req.urlWithParams}`);
-        break;
-      default:
-        if (ev instanceof HttpErrorResponse) {
-          console.warn(
-            '未可知错误,大部分是由于后端不支持跨域CORS或无效配置引起,请参考 https://ng-alain.com/docs/server 解决跨域问题',
-            ev
-          );
-        }
-        break;
-    }
-    if (ev instanceof HttpErrorResponse) {
-      return throwError(ev);
-    } else {
-      return of(ev);
-    }
-  }
-
-  private getAdditionalHeaders(headers?: HttpHeaders): { [name: string]: string } {
+  private fillHeaders(headers?: HttpHeaders): { [name: string]: string } {
     const res: { [name: string]: string } = {};
     const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
     if (!headers?.has('Accept-Language') && lang) {
       res['Accept-Language'] = lang;
     }
-
+    let token = this.storageSvc.getAuthorizationToken();
+    if (token !== null) {
+      res['Authorization'] = `Bearer ${token}`;
+    }
     return res;
   }
 
   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
-    // 统一加上服务端前缀
     let url = req.url;
     if (!url.startsWith('https://') && !url.startsWith('http://')) {
       const { baseUrl } = environment.api;
       url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
     }
-
-    const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) });
+    const newReq = req.clone({ url, setHeaders: this.fillHeaders(req.headers) });
     return next.handle(newReq).pipe(
-      mergeMap(ev => {
-        // 允许统一对请求错误处理
-        if (ev instanceof HttpResponseBase) {
-          return this.handleData(ev, newReq, next);
+      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;
+          }
+          return of(httpEvent);
+        } else {
+          return of(httpEvent);
         }
-        // 若一切都正常,则后续操作
-        return of(ev);
       }),
-      catchError((err: HttpErrorResponse) => this.handleData(err, newReq, next))
+      catchError((err: HttpErrorResponse) => {
+        this.checkStatus(err);
+        console.warn(`${err.status} == ${err.message}`)
+        return throwError(err);
+      })
     );
   }
 }

+ 1 - 2
web-app/src/app/core/startup/startup.service.ts

@@ -12,7 +12,6 @@ import { NzIconService } from 'ng-zorro-antd/icon';
 
 import { ICONS } from '../../../style-icons';
 import { ICONS_AUTO } from '../../../style-icons-auto';
-
 /**
  * Used for application startup
  * Generally used to get the basic data of the application, like: Menu Data, User Data, etc.
@@ -36,7 +35,7 @@ export class StartupService {
 
     private viaHttp(): Observable<void> {
       const defaultLang = this.i18n.defaultLang;
-      return zip(this.i18n.loadLangData(defaultLang), this.httpClient.get('assets/tmp/app-data.json')).pipe(
+      return zip(this.i18n.loadLangData(defaultLang), this.httpClient.get('http://localhost:4200/assets/tmp/app-data.json')).pipe(
         catchError((res: NzSafeAny) => {
           console.warn(`StartupService.load: Network request failed`, res);
           setTimeout(() => this.router.navigateByUrl(`/exception/500`));

+ 5 - 0
web-app/src/app/pojo/Message.ts

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

+ 13 - 0
web-app/src/app/pojo/Monitor.ts

@@ -0,0 +1,13 @@
+export class Monitor {
+  id!: number;
+  name!: string;
+  app!: string;
+  host!: string;
+  intervals!: number;
+  status!: number;
+  description!: string;
+  creator!: string;
+  modifier!: string;
+  gmtCreate!: number;
+  gmtUpdate!: number;
+}

+ 6 - 0
web-app/src/app/pojo/Param.ts

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

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

@@ -0,0 +1,9 @@
+export class ParamDefine {
+  name!: string;
+  field!: string;
+  type!: string;
+  required: boolean | undefined;
+  range: string | undefined;
+  limit: number | undefined;
+  option: string | undefined;
+}

+ 1 - 0
web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.html

@@ -0,0 +1 @@
+<p>monitor-detail works!</p>

+ 25 - 0
web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MonitorDetailComponent } from './monitor-detail.component';
+
+describe('MonitorDetailComponent', () => {
+  let component: MonitorDetailComponent;
+  let fixture: ComponentFixture<MonitorDetailComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MonitorDetailComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MonitorDetailComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 16 - 0
web-app/src/app/routes/monitor/monitor-detail/monitor-detail.component.ts

@@ -0,0 +1,16 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-monitor-detail',
+  templateUrl: './monitor-detail.component.html',
+  styles: [
+  ]
+})
+export class MonitorDetailComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit(): void {
+  }
+
+}

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

@@ -0,0 +1 @@
+<p>monitor-modify works!</p>

+ 25 - 0
web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MonitorEditComponent } from './monitor-edit.component';
+
+describe('MonitorModifyComponent', () => {
+  let component: MonitorEditComponent;
+  let fixture: ComponentFixture<MonitorEditComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MonitorEditComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MonitorEditComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 16 - 0
web-app/src/app/routes/monitor/monitor-edit/monitor-edit.component.ts

@@ -0,0 +1,16 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-monitor-modify',
+  templateUrl: './monitor-edit.component.html',
+  styles: [
+  ]
+})
+export class MonitorEditComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit(): void {
+  }
+
+}

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

@@ -0,0 +1,2 @@
+<nz-breadcrumb [nzAutoGenerate]="true"></nz-breadcrumb>
+<p>monitor-list works!</p>

+ 25 - 0
web-app/src/app/routes/monitor/monitor-list/monitor-list.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MonitorListComponent } from './monitor-list.component';
+
+describe('MonitorListComponent', () => {
+  let component: MonitorListComponent;
+  let fixture: ComponentFixture<MonitorListComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MonitorListComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MonitorListComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 16 - 0
web-app/src/app/routes/monitor/monitor-list/monitor-list.component.ts

@@ -0,0 +1,16 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-monitor-list',
+  templateUrl: './monitor-list.component.html',
+  styles: [
+  ]
+})
+export class MonitorListComponent implements OnInit {
+
+  constructor() { }
+
+  ngOnInit(): void {
+  }
+
+}

+ 136 - 0
web-app/src/app/routes/monitor/monitor-new/monitor-new.component.html

@@ -0,0 +1,136 @@
+<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>
+
+<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-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" 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">
+          探测
+        </button>
+        <button nz-button nzType="primary" type="submit">
+          确定
+        </button>
+        <button nz-button nzType="primary" nzDanger="true" type="reset">
+          取消
+        </button>
+      </div>
+    </div>
+  </form>
+</div>

+ 25 - 0
web-app/src/app/routes/monitor/monitor-new/monitor-new.component.spec.ts

@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MonitorNewComponent } from './monitor-new.component';
+
+describe('MonitorAddComponent', () => {
+  let component: MonitorNewComponent;
+  let fixture: ComponentFixture<MonitorNewComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ MonitorNewComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(MonitorNewComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 77 - 0
web-app/src/app/routes/monitor/monitor-new/monitor-new.component.ts

@@ -0,0 +1,77 @@
+import { Component, OnInit } from '@angular/core';
+import {ParamDefine} from "../../../pojo/ParamDefine";
+import {AppDefineService} from "../../../service/app-define.service";
+import {ActivatedRoute, ParamMap, Router} from "@angular/router";
+import {switchMap} from "rxjs/operators";
+import {FormBuilder, FormControl, FormGroup} from "@angular/forms";
+import {I18NService} from "@core";
+import {Param} from "../../../pojo/Param";
+import {Monitor} from "../../../pojo/Monitor";
+import {MonitorService} from "../../../service/monitor.service";
+import {NzNotificationService} from "ng-zorro-antd/notification";
+
+@Component({
+  selector: 'app-monitor-add',
+  templateUrl: './monitor-new.component.html',
+  styles: [
+  ]
+})
+export class MonitorNewComponent implements OnInit {
+
+  paramDefines!: ParamDefine[];
+  params!: Param[];
+  monitor!: Monitor;
+  profileForm: FormGroup = new FormGroup({});
+  detected: boolean = true;
+  passwordVisible!: boolean;
+  constructor(private appDefineSvc: AppDefineService,
+              private monitorSvc: MonitorService,
+              private route: ActivatedRoute,
+              private router: Router,
+              private notifySvc: NzNotificationService,
+              private i18n: I18NService,
+              private formBuilder: FormBuilder) {
+    this.monitor = new Monitor();
+  }
+
+  ngOnInit(): void {
+    const paramDefine$ = this.route.queryParamMap.pipe(
+      switchMap((paramMap: ParamMap) => {
+        this.monitor.app = paramMap.get("app") || '';
+        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 = new Param();
+          param.field = define.field;
+          param.type = define.type === "number" ? 0 : 1;
+          this.params.push(param);
+        })
+      } else {
+        console.warn(message.msg);
+      }
+      paramDefine$.unsubscribe();
+    });
+  }
+
+  onSubmit() {
+    let addMonitor = {
+      "detected": this.detected,
+      "monitor": this.monitor,
+      "params": this.params
+    };
+    this.monitorSvc.newMonitor(addMonitor)
+      .subscribe(message => {
+        if (message.code === 0) {
+          this.notifySvc.success("新增监控成功", "");
+          this.router.navigateByUrl("/monitors")
+        } else {
+          this.notifySvc.error("新增监控失败", message.msg);
+        }
+    })
+  }
+
+}

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

@@ -1,7 +1,16 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
+import {MonitorListComponent} from "./monitor-list/monitor-list.component";
+import {MonitorNewComponent} from "./monitor-new/monitor-new.component";
+import {MonitorEditComponent} from "./monitor-edit/monitor-edit.component";
+import {MonitorDetailComponent} from "./monitor-detail/monitor-detail.component";
 
 const routes: Routes = [
+  { path: '', component: MonitorNewComponent },
+  { path: 'new', component: MonitorNewComponent },
+  { path: ':monitorId/edit', component: MonitorEditComponent },
+  { path: ':monitorId', component: MonitorDetailComponent },
+  { path: '**', component: MonitorListComponent }
 ];
 
 @NgModule({

+ 20 - 5
web-app/src/app/routes/monitor/monitor.module.ts

@@ -1,14 +1,29 @@
 import { NgModule, Type } from '@angular/core';
 import { SharedModule } from '@shared';
 import { MonitorRoutingModule } from './monitor-routing.module';
+import {MonitorNewComponent} from "./monitor-new/monitor-new.component";
+import {MonitorEditComponent} from "./monitor-edit/monitor-edit.component";
+import {MonitorListComponent} from "./monitor-list/monitor-list.component";
+import {MonitorDetailComponent} from "./monitor-detail/monitor-detail.component";
+import {NzBreadCrumbModule} from "ng-zorro-antd/breadcrumb";
+import {NzDividerModule} from "ng-zorro-antd/divider";
+import {NzSwitchModule} from "ng-zorro-antd/switch";
 
-const COMPONENTS: Type<void>[] = [];
+const COMPONENTS: Type<void>[] = [
+  MonitorNewComponent,
+  MonitorEditComponent,
+  MonitorListComponent,
+  MonitorDetailComponent
+];
 
 @NgModule({
-  imports: [
-    SharedModule,
-    MonitorRoutingModule
-  ],
+    imports: [
+        SharedModule,
+        MonitorRoutingModule,
+        NzBreadCrumbModule,
+        NzDividerModule,
+        NzSwitchModule
+    ],
   declarations: COMPONENTS,
 })
 export class MonitorModule { }

+ 3 - 4
web-app/src/app/routes/routes-routing.module.ts

@@ -21,12 +21,11 @@ const routes: Routes = [
     component: LayoutBasicComponent,
     canActivate: [SimpleGuard],
     children: [
-      { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
+      // todo 根据路由自动生成面包屑
+      { path: '', redirectTo: 'dashboard', pathMatch: 'full'},
       { path: 'dashboard', component: DashboardComponent, data: { title: '仪表盘' } },
       { path: 'exception', loadChildren: () => import('./exception/exception.module').then(m => m.ExceptionModule) },
-      // 业务子模块
-      // { path: 'widgets', loadChildren: () => import('./widgets/widgets.module').then(m => m.WidgetsModule) },
-    { path: 'monitor', loadChildren: () => import('./monitor/monitor.module').then((m) => m.MonitorModule) },]
+      { path: 'monitors', loadChildren: () => import('./monitor/monitor.module').then((m) => m.MonitorModule) },]
   },
   // 空白布局
   // {

+ 16 - 0
web-app/src/app/service/app-define.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { AppDefineService } from './app-define.service';
+
+describe('AppDefineService', () => {
+  let service: AppDefineService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(AppDefineService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 22 - 0
web-app/src/app/service/app-define.service.ts

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

+ 16 - 0
web-app/src/app/service/local-storage.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { LocalStorageService } from './local-storage.service';
+
+describe('LocalStorageService', () => {
+  let service: LocalStorageService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(LocalStorageService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 29 - 0
web-app/src/app/service/local-storage.service.ts

@@ -0,0 +1,29 @@
+import { Injectable } from '@angular/core';
+
+const Authorization = 'Authorization';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class LocalStorageService {
+
+  constructor() { }
+
+  public putData(key: string, value: string) {
+    localStorage.setItem(key, value);
+  }
+
+  public getData(key: string): string | null {
+    const data = localStorage.getItem(key);
+    return data === null ? null : data;
+  }
+
+  public getAuthorizationToken(): string | null {
+    return this.getData(Authorization);
+  }
+
+  public storageAuthorizationToken(token: string) {
+    return this.putData(Authorization, token);
+  }
+
+}

+ 16 - 0
web-app/src/app/service/monitor.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { MonitorService } from './monitor.service';
+
+describe('MonitorService', () => {
+  let service: MonitorService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(MonitorService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 19 - 0
web-app/src/app/service/monitor.service.ts

@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import {HttpClient} from "@angular/common/http";
+import {Observable} from "rxjs";
+import {Message} from "../pojo/Message";
+
+const monitor_uri = "/monitor";
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MonitorService {
+
+  constructor(private http : HttpClient) { }
+
+  public newMonitor(body: any) : Observable<Message> {
+    return this.http.post<Message>(monitor_uri, body);
+  }
+
+}

+ 16 - 0
web-app/src/app/service/my-service.service.spec.ts

@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { MyServiceService } from './my-service.service';
+
+describe('MyServiceService', () => {
+  let service: MyServiceService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(MyServiceService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});

+ 9 - 0
web-app/src/app/service/my-service.service.ts

@@ -0,0 +1,9 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MyServiceService {
+
+  constructor() { }
+}

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

@@ -19,7 +19,7 @@
           "text": "仪表盘",
           "i18n": "menu.dashboard",
           "icon": "anticon-dashboard",
-          "link": "/dashboard/v1"
+          "link": "/dashboard"
         }
       ]
     },
@@ -35,18 +35,18 @@
           "icon": "anticon-cloud",
           "children": [
             {
-              "text": "仪表盘V1",
-              "link": "/dashboard/v1",
+              "text": "http",
+              "link": "/monitors",
               "i18n": "monitor.app.http"
             },
             {
-              "text": "分析页",
-              "link": "/dashboard/analysis",
+              "text": "ping",
+              "link": "/monitors",
               "i18n": "monitor.app.ping"
             },
             {
-              "text": "监控页",
-              "link": "/dashboard/monitor",
+              "text": "telnet",
+              "link": "/monitors",
               "i18n": "monitor.app.telnet"
             }
           ]
@@ -58,17 +58,17 @@
           "children": [
             {
               "text": "Mysql",
-              "link": "/dashboard/v1",
+              "link": "/monitors",
               "i18n": "monitor.app.mysql"
             },
             {
               "text": "Oracle",
-              "link": "/dashboard/analysis",
+              "link": "/monitors",
               "i18n": "monitor.app.oracle"
             },
             {
               "text": "Redis",
-              "link": "/dashboard/monitor",
+              "link": "/monitors",
               "i18n": "monitor.app.redis"
             }
           ]

+ 1 - 2
web-app/src/environments/environment.prod.ts

@@ -5,7 +5,6 @@ export const environment = {
   useHash: true,
   api: {
     baseUrl: './',
-    refreshTokenEnabled: true,
-    refreshTokenType: 'auth-refresh'
+    refreshTokenEnabled: true
   }
 } as Environment;

+ 4 - 5
web-app/src/environments/environment.ts

@@ -5,17 +5,16 @@
 import { DelonMockModule } from '@delon/mock';
 import { Environment } from '@delon/theme';
 
-import * as MOCKDATA from '../../_mock';
+import * as MOCK_DATA from '../../_mock';
 
 export const environment = {
   production: false,
   useHash: true,
   api: {
-    baseUrl: './',
-    refreshTokenEnabled: true,
-    refreshTokenType: 'auth-refresh'
+    baseUrl: 'http://localhost:8080/',
+    refreshTokenEnabled: true
   },
-  modules: [DelonMockModule.forRoot({ data: MOCKDATA })]
+  modules: [DelonMockModule.forRoot({ data: MOCK_DATA })]
 } as Environment;
 
 /*