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

This commit is contained in:
tomsun28
2021-11-30 22:16:38 +08:00
parent 619e407158
commit 090e6e8c16
37 changed files with 663 additions and 172 deletions

View File

@@ -22,6 +22,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~12.2.0", "@angular/animations": "~12.2.0",
"@angular/cdk": "~12.2.0",
"@angular/common": "~12.2.0", "@angular/common": "~12.2.0",
"@angular/compiler": "~12.2.0", "@angular/compiler": "~12.2.0",
"@angular/core": "~12.2.0", "@angular/core": "~12.2.0",
@@ -29,43 +30,38 @@
"@angular/platform-browser": "~12.2.0", "@angular/platform-browser": "~12.2.0",
"@angular/platform-browser-dynamic": "~12.2.0", "@angular/platform-browser-dynamic": "~12.2.0",
"@angular/router": "~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/abc": "^12.4.2",
"@delon/acl": "^12.4.2", "@delon/acl": "^12.4.2",
"@delon/auth": "^12.4.2", "@delon/auth": "^12.4.2",
"@delon/cache": "^12.4.2", "@delon/cache": "^12.4.2",
"@delon/chart": "^12.4.2",
"@delon/form": "^12.4.2", "@delon/form": "^12.4.2",
"@delon/mock": "^12.4.2", "@delon/mock": "^12.4.2",
"@delon/theme": "^12.4.2", "@delon/theme": "^12.4.2",
"@delon/util": "^12.4.2", "@delon/util": "^12.4.2",
"@delon/chart": "^12.4.2",
"ajv": "^8.6.2", "ajv": "^8.6.2",
"ajv-formats": "^2.1.1", "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": { "devDependencies": {
"@angular-devkit/build-angular": "~12.2.13", "@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/builder": "~12.3.1",
"@angular-eslint/eslint-plugin": "~12.3.1", "@angular-eslint/eslint-plugin": "~12.3.1",
"@angular-eslint/eslint-plugin-template": "~12.3.1", "@angular-eslint/eslint-plugin-template": "~12.3.1",
"@angular-eslint/schematics": "~12.3.1", "@angular-eslint/schematics": "~12.3.1",
"@angular-eslint/template-parser": "~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/eslint-plugin": "~4.29.2",
"@typescript-eslint/parser": "~4.29.2", "@typescript-eslint/parser": "~4.29.2",
"eslint": "^7.32.0", "eslint": "^7.32.0",
@@ -74,19 +70,24 @@
"eslint-plugin-jsdoc": "~36.0.7", "eslint-plugin-jsdoc": "~36.0.7",
"eslint-plugin-prefer-arrow": "~1.2.3", "eslint-plugin-prefer-arrow": "~1.2.3",
"eslint-plugin-prettier": "^2.2.1", "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": "^12.4.2",
"ng-alain-plugin-theme": "^12.0.0", "ng-alain-plugin-theme": "^12.0.0",
"prettier": "^2.2.1",
"source-map-explorer": "^2.5.2", "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": "^13.13.1",
"stylelint-config-prettier": "^8.0.2", "stylelint-config-prettier": "^8.0.2",
"stylelint-config-rational-order": "^0.1.2", "stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.4.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": { "lint-staged": {
"(src)/**/*.{html,ts}": [ "(src)/**/*.{html,ts}": [

View File

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

View File

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

View File

@@ -92,7 +92,7 @@ export class I18NService extends AlainI18nBaseService {
} }
loadLangData(lang: string): Observable<NzSafeAny> { 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 { use(lang: string, data: Record<string, unknown>): void {

View File

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

View File

@@ -15,8 +15,9 @@ import { environment } from '@env/environment';
import { NzNotificationService } from 'ng-zorro-antd/notification'; import { NzNotificationService } from 'ng-zorro-antd/notification';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs'; import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, filter, mergeMap, switchMap, take } from 'rxjs/operators'; 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: '服务器成功返回请求的数据。', 200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。', 201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。', 202: '一个请求已经进入后台排队(异步任务)。',
@@ -40,15 +41,10 @@ const CODEMESSAGE: { [key: number]: string } = {
@Injectable() @Injectable()
export class DefaultInterceptor implements HttpInterceptor { export class DefaultInterceptor implements HttpInterceptor {
private refreshTokenEnabled = environment.api.refreshTokenEnabled; private refreshTokenEnabled = environment.api.refreshTokenEnabled;
private refreshTokenType: 're-request' | 'auth-refresh' = environment.api.refreshTokenType;
private refreshToking = false; private refreshToking = false;
private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null); private refreshToken$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private injector: Injector) { constructor(private injector: Injector, private storageSvc: LocalStorageService) { }
if (this.refreshTokenType === 'auth-refresh') {
this.buildAuthRefresh();
}
}
private get notification(): NzNotificationService { private get notification(): NzNotificationService {
return this.injector.get(NzNotificationService); return this.injector.get(NzNotificationService);
@@ -67,12 +63,11 @@ export class DefaultInterceptor implements HttpInterceptor {
} }
private checkStatus(ev: HttpResponseBase): void { private checkStatus(ev: HttpResponseBase): void {
if ((ev.status >= 200 && ev.status < 300) || ev.status === 401) { if (ev.status >= 200 && ev.status < 500) {
return; return;
} }
const errorText = CODE_MESSAGE[ev.status] || ev.statusText;
const errortext = CODEMESSAGE[ev.status] || ev.statusText; this.notification.error(`抱歉服务器繁忙 ${ev.status}: ${ev.url}`, errorText);
this.notification.error(`请求错误 ${ev.status}: ${ev.url}`, errortext);
} }
/** /**
@@ -109,6 +104,7 @@ export class DefaultInterceptor implements HttpInterceptor {
this.refreshToking = false; this.refreshToking = false;
this.refreshToken$.next(res); this.refreshToken$.next(res);
// 重新保存新 token // 重新保存新 token
this.storageSvc.storageAuthorizationToken(res);
this.tokenSrv.set(res); this.tokenSrv.set(res);
// 重新发起请求 // 重新发起请求
return next.handle(this.reAttachToken(req)); return next.handle(this.reAttachToken(req));
@@ -124,138 +120,69 @@ export class DefaultInterceptor implements HttpInterceptor {
/** /**
* Token * Token
* *
* > `@delon/auth` Token
*/ */
private reAttachToken(req: HttpRequest<any>): HttpRequest<any> { private reAttachToken(req: HttpRequest<any>): HttpRequest<any> {
// 以下示例是以 NG-ALAIN 默认使用 `SimpleInterceptor` let token = this.storageSvc.getAuthorizationToken();
const token = this.tokenSrv.get()?.token;
return req.clone({ return req.clone({
setHeaders: { 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 { private toLogin(): void {
this.notification.error(`未登录或登录已过期,请重新登录。`, ``); this.notification.error(`未登录或登录已过期,请重新登录。`, ``);
this.goTo(this.tokenSrv.login_url!); this.goTo(this.tokenSrv.login_url!);
} }
private handleData(ev: HttpResponseBase, req: HttpRequest<any>, next: HttpHandler): Observable<any> { private fillHeaders(headers?: HttpHeaders): { [name: string]: string } {
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 } {
const res: { [name: string]: string } = {}; const res: { [name: string]: string } = {};
const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang; const lang = this.injector.get(ALAIN_I18N_TOKEN).currentLang;
if (!headers?.has('Accept-Language') && lang) { if (!headers?.has('Accept-Language') && lang) {
res['Accept-Language'] = lang; res['Accept-Language'] = lang;
} }
let token = this.storageSvc.getAuthorizationToken();
if (token !== null) {
res['Authorization'] = `Bearer ${token}`;
}
return res; return res;
} }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// 统一加上服务端前缀
let url = req.url; let url = req.url;
if (!url.startsWith('https://') && !url.startsWith('http://')) { if (!url.startsWith('https://') && !url.startsWith('http://')) {
const { baseUrl } = environment.api; const { baseUrl } = environment.api;
url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url); url = baseUrl + (baseUrl.endsWith('/') && url.startsWith('/') ? url.substring(1) : url);
} }
const newReq = req.clone({ url, setHeaders: this.fillHeaders(req.headers) });
const newReq = req.clone({ url, setHeaders: this.getAdditionalHeaders(req.headers) });
return next.handle(newReq).pipe( return next.handle(newReq).pipe(
mergeMap(ev => { mergeMap(httpEvent => {
// 允许统一对请求错误处理 if (httpEvent instanceof HttpResponseBase) {
if (ev instanceof HttpResponseBase) { // 处理token过期自动刷新
return this.handleData(ev, newReq, next); 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);
})
); );
} }
} }

View File

@@ -12,7 +12,6 @@ import { NzIconService } from 'ng-zorro-antd/icon';
import { ICONS } from '../../../style-icons'; import { ICONS } from '../../../style-icons';
import { ICONS_AUTO } from '../../../style-icons-auto'; import { ICONS_AUTO } from '../../../style-icons-auto';
/** /**
* Used for application startup * Used for application startup
* Generally used to get the basic data of the application, like: Menu Data, User Data, etc. * 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> { private viaHttp(): Observable<void> {
const defaultLang = this.i18n.defaultLang; 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) => { catchError((res: NzSafeAny) => {
console.warn(`StartupService.load: Network request failed`, res); console.warn(`StartupService.load: Network request failed`, res);
setTimeout(() => this.router.navigateByUrl(`/exception/500`)); setTimeout(() => this.router.navigateByUrl(`/exception/500`));

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

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

View File

@@ -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();
});
});

View File

@@ -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 {
}
}

View File

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

View File

@@ -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();
});
});

View File

@@ -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 {
}
}

View File

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

View File

@@ -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();
});
});

View File

@@ -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 {
}
}

View File

@@ -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>

View File

@@ -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();
});
});

View File

@@ -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);
}
})
}
}

View File

@@ -1,7 +1,16 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; 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 = [ const routes: Routes = [
{ path: '', component: MonitorNewComponent },
{ path: 'new', component: MonitorNewComponent },
{ path: ':monitorId/edit', component: MonitorEditComponent },
{ path: ':monitorId', component: MonitorDetailComponent },
{ path: '**', component: MonitorListComponent }
]; ];
@NgModule({ @NgModule({

View File

@@ -1,13 +1,28 @@
import { NgModule, Type } from '@angular/core'; import { NgModule, Type } from '@angular/core';
import { SharedModule } from '@shared'; import { SharedModule } from '@shared';
import { MonitorRoutingModule } from './monitor-routing.module'; 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({ @NgModule({
imports: [ imports: [
SharedModule, SharedModule,
MonitorRoutingModule MonitorRoutingModule,
NzBreadCrumbModule,
NzDividerModule,
NzSwitchModule
], ],
declarations: COMPONENTS, declarations: COMPONENTS,
}) })

View File

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

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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();
});
});

View File

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

View File

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

View File

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

View File

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