From d03457fa584d4f1760ee1c7b68f6fa97aa6797d2 Mon Sep 17 00:00:00 2001 From: tomsun28 Date: Wed, 8 Dec 2021 12:11:35 +0800 Subject: [PATCH] =?UTF-8?q?[web-app]=20=E4=BB=AA=E8=A1=A8=E7=9B=98-?= =?UTF-8?q?=E7=9B=91=E6=8E=A7=E6=80=BB=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/routes/dashboard/ColorTheme.ts | 134 +++++++++++++ .../routes/dashboard/dashboard.component.html | 29 +-- .../routes/dashboard/dashboard.component.less | 62 +----- .../routes/dashboard/dashboard.component.ts | 189 ++++++++++++++---- web-app/src/app/routes/routes.module.ts | 7 +- web-app/src/app/service/monitor.service.ts | 6 + 6 files changed, 295 insertions(+), 132 deletions(-) create mode 100644 web-app/src/app/routes/dashboard/ColorTheme.ts diff --git a/web-app/src/app/routes/dashboard/ColorTheme.ts b/web-app/src/app/routes/dashboard/ColorTheme.ts new file mode 100644 index 0000000..01731ef --- /dev/null +++ b/web-app/src/app/routes/dashboard/ColorTheme.ts @@ -0,0 +1,134 @@ +import {ThemeOption} from "ngx-echarts"; + +export const CoolTheme: ThemeOption = { + color: [ + '#b21ab4', + '#6f0099', + '#2a2073', + '#0b5ea8', + '#17aecc', + '#b3b3ff', + '#eb99ff', + '#fae6ff', + '#e6f2ff', + '#eeeeee' + ], + + title: { + textStyle: { + fontWeight: 'normal', + color: '#00aecd', + center: true + } + }, + + visualMap: { + color: ['#00aecd', '#a2d4e6'] + }, + + toolbox: { + color: ['#00aecd', '#00aecd', '#00aecd', '#00aecd'] + }, + + tooltip: { + backgroundColor: 'rgba(0,0,0,0.5)', + axisPointer: { + // Axis indicator, coordinate trigger effective + type: 'line', // The default is a straight line: 'line' | 'shadow' + lineStyle: { + // Straight line indicator style settings + color: '#00aecd', + type: 'dashed' + }, + crossStyle: { + color: '#00aecd' + }, + shadowStyle: { + // Shadow indicator style settings + color: 'rgba(200,200,200,0.3)' + } + } + }, + + // Area scaling controller + dataZoom: { + dataBackgroundColor: '#eee', // Data background color + fillerColor: 'rgba(144,197,237,0.2)', // Fill the color + handleColor: '#00aecd' // Handle color + }, + + timeline: { + lineStyle: { + color: '#00aecd' + }, + controlStyle: { + color: '#00aecd', + borderColor: '00aecd' + } + }, + + candlestick: { + itemStyle: { + color: '#00aecd', + color0: '#a2d4e6' + }, + lineStyle: { + width: 1, + color: '#00aecd', + color0: '#a2d4e6' + }, + areaStyle: { + color: '#b21ab4', + color0: '#0b5ea8' + } + }, + + chord: { + padding: 4, + itemStyle: { + color: '#b21ab4', + borderWidth: 1, + borderColor: 'rgba(128, 128, 128, 0.5)' + }, + lineStyle: { + color: 'rgba(128, 128, 128, 0.5)' + }, + areaStyle: { + color: '#0b5ea8' + } + }, + + graph: { + itemStyle: { + color: '#b21ab4' + }, + linkStyle: { + color: '#2a2073' + } + }, + + map: { + itemStyle: { + color: '#c12e34' + }, + areaStyle: { + color: '#ddd' + }, + label: { + color: '#c12e34' + } + }, + + gauge: { + axisLine: { + lineStyle: { + color: [ + [0.2, '#dddddd'], + [0.8, '#00aecd'], + [1, '#f5ccff'] + ], + width: 8 + } + } + } +}; diff --git a/web-app/src/app/routes/dashboard/dashboard.component.html b/web-app/src/app/routes/dashboard/dashboard.component.html index 4954878..fd3ae52 100644 --- a/web-app/src/app/routes/dashboard/dashboard.component.html +++ b/web-app/src/app/routes/dashboard/dashboard.component.html @@ -1,27 +1,6 @@ - -
-
- - - - -
-
- -
- -
-
+
+
+
+
diff --git a/web-app/src/app/routes/dashboard/dashboard.component.less b/web-app/src/app/routes/dashboard/dashboard.component.less index 6b1d3e6..e6bd4b0 100644 --- a/web-app/src/app/routes/dashboard/dashboard.component.less +++ b/web-app/src/app/routes/dashboard/dashboard.component.less @@ -1,61 +1,3 @@ -@import '~@delon/theme/index'; -:host ::ng-deep { - .map-chart { - height: 457px; - padding-top: 24px; - text-align: center; - img { - display: inline-block; - max-width: 100%; - max-height: 437px; - } - } - .pie-card { - .pie-stat { - font-size: 24px !important; - } - } - .active-chart { - position: relative; - g2-mini-area { - margin-top: 32px; - } - .active-grid { - p { - position: absolute; - top: 80px; - width: 100%; - padding-bottom: 4px; - border-bottom: 1px dashed #e9e9e9; - } - p:last-child { - top: 115px; - } - } - .active-legend { - position: relative; - height: 20px; - margin-top: 8px; - font-size: 0; - line-height: 20px; - span { - display: inline-block; - width: 33.33%; - font-size: 12px; - text-align: center; - } - span:first-child { - text-align: left; - } - span:last-child { - text-align: right; - } - } - } - - @media screen and (max-width: @screen-lg) { - .map-chart { - height: auto; - } - } +.demo-chart { + height: auto; } diff --git a/web-app/src/app/routes/dashboard/dashboard.component.ts b/web-app/src/app/routes/dashboard/dashboard.component.ts index 6002576..0b154dc 100644 --- a/web-app/src/app/routes/dashboard/dashboard.component.ts +++ b/web-app/src/app/routes/dashboard/dashboard.component.ts @@ -1,6 +1,17 @@ -import {ChangeDetectionStrategy, Component, ViewChild} from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Inject, + OnDestroy, + OnInit +} from '@angular/core'; import {NzMessageService} from "ng-zorro-antd/message"; -import {G2PieClickItem, G2PieComponent, G2PieData} from "@delon/chart/pie"; +import {MonitorService} from "../../service/monitor.service"; +import { EChartsOption } from 'echarts'; +import {I18NService} from "@core"; +import {ALAIN_I18N_TOKEN} from "@delon/theme"; +import {Router} from "@angular/router"; @Component({ selector: 'app-dashboard', @@ -8,55 +19,147 @@ import {G2PieClickItem, G2PieComponent, G2PieData} from "@delon/chart/pie"; styleUrls: ['./dashboard.component.less'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DashboardComponent { +export class DashboardComponent implements OnInit, OnDestroy { - @ViewChild('pie', { static: false }) readonly pie!: G2PieComponent; - salesPieData: G2PieData[] = []; - total = ''; + constructor(private msg: NzMessageService, + private monitorSvc: MonitorService, + @Inject(ALAIN_I18N_TOKEN) private i18nSvc: I18NService, + private router: Router, + private cdr: ChangeDetectorRef){} - constructor(private msg: NzMessageService){} + interval$!: number; + appsCountLoading: boolean = true; + appsCountTableData: any[] = []; + appsCountEChartOption!: EChartsOption; ngOnInit(): void { + this.appsCountLoading = true; this.refresh(); + this.appsCountLoading = false; + // https://stackoverflow.com/questions/43908009/why-is-setinterval-in-an-angular-service-only-firing-one-time + this.interval$ = setInterval(this.refresh.bind(this), 10000); + } + + ngOnDestroy(): void { + clearInterval(this.interval$); } refresh(): void { - const rv = (min: number = 0, max: number = 5000) => Math.floor(Math.random() * (max - min + 1) + min); - this.salesPieData = [ - { - x: '应用服务', - y: rv(), - }, - { - x: '数据库', - y: rv(), - }, - { - x: '中间件', - y: rv(), - }, - { - x: '自定义', - y: rv(), - }, - { - x: '其它', - y: rv(), - }, - ]; - this.total = `${this.salesPieData.reduce((pre, now) => now.y + pre, 0).toFixed(2)}`; - if (this.pie) { - // 等待组件渲染 - setTimeout(() => this.pie.changeData()); + let dashboard$ = this.monitorSvc.getAppsMonitorSummary() + .subscribe(message => { + dashboard$.unsubscribe(); + if (message.code === 0) { + // {app:'linux',size: 12} + let apps: any[] = message.data.apps; + this.appsCountTableData = []; + let total = 0; + apps.forEach(app => { + let appName = this.i18nSvc.fanyi('monitor.app.' + app.app); + this.appsCountTableData.push({ + // 自定义属性 + app: app.app, + // 默认属性 + name: appName, + value: app.size + }); + total = total + app.size? app.size : 0; + }); + + this.appsCountEChartOption = { + title: { + text: '监控总览', + subtext: '监控类型纳管数量分布', + left: 'center' + }, + tooltip: { + trigger: 'item', + formatter: '{a}
{b} : {c}个监控 占比({d}%)' + }, + legend: { + itemWidth: 80, + itemHeight: 20, + right: 0, + orient: 'vertical' + }, + calculable: true, + series: [ + { + name: '总量', + type: 'pie', + selectedMode: 'single', + color: '#722ED1', + radius: [0, '30%'], + label: { + position: 'center', + fontSize: 15, + color: '#ffffff', + fontStyle: 'oblique', + formatter: '{a}:{c}', + }, + labelLine: { + show: false + }, + data: [ + { value: total, name: '监控总量' }, + ] + }, + { + name: '纳管数量分布', + type: 'pie', + radius: ['45%', '65%'], + labelLine: { + length: 30 + }, + label: { + formatter: '{a|{a}}{abg|}\n{hr|}\n {b|{b}:}{c} {per|{d}%} ', + backgroundColor: '#F6F8FC', + borderColor: '#8C8D8E', + borderWidth: 1, + borderRadius: 4, + rich: { + a: { + color: '#6E7079', + lineHeight: 22, + align: 'center' + }, + hr: { + borderColor: '#8C8D8E', + width: '100%', + borderWidth: 1, + height: 0 + }, + b: { + color: '#4C5058', + fontSize: 14, + fontWeight: 'bold', + lineHeight: 33 + }, + per: { + color: '#fff', + backgroundColor: '#4C5058', + padding: [3, 4], + borderRadius: 4 + } + } + }, + data: this.appsCountTableData + } + ] + }; + this.cdr.detectChanges(); + } + }, error => { + console.error(error); + dashboard$.unsubscribe(); + }); + } + + onChartClick(click: any) { + if (click != undefined) { + let app = click.data?.app; + if (app != undefined) { + this.router.navigate(['/monitors'], { queryParams: { app: app } }); + } } } - - format(val: number): string { - return `${val.toFixed()}`; - } - - handleClick(data: G2PieClickItem): void { - this.msg.info(`${data.item.x} - ${data.item.y}`); - } - } diff --git a/web-app/src/app/routes/routes.module.ts b/web-app/src/app/routes/routes.module.ts index ceca254..e1427b9 100644 --- a/web-app/src/app/routes/routes.module.ts +++ b/web-app/src/app/routes/routes.module.ts @@ -1,9 +1,9 @@ import { NgModule, Type } from '@angular/core'; import { SharedModule } from '@shared'; -import {G2PieModule} from "@delon/chart/pie"; -import {G2WaterWaveModule} from "@delon/chart/water-wave"; // dashboard pages import { DashboardComponent } from './dashboard/dashboard.component'; +import { RouteRoutingModule } from './routes-routing.module'; +import {NgxEchartsModule} from "ngx-echarts"; // single pages import { CallbackComponent } from './passport/callback.component'; import { UserLockComponent } from './passport/lock/lock.component'; @@ -11,7 +11,6 @@ import { UserLockComponent } from './passport/lock/lock.component'; import { UserLoginComponent } from './passport/login/login.component'; import { UserRegisterResultComponent } from './passport/register-result/register-result.component'; import { UserRegisterComponent } from './passport/register/register.component'; -import { RouteRoutingModule } from './routes-routing.module'; const COMPONENTS: Array> = [ DashboardComponent, @@ -25,7 +24,7 @@ const COMPONENTS: Array> = [ ]; @NgModule({ - imports: [SharedModule, RouteRoutingModule, G2PieModule, G2WaterWaveModule], + imports: [SharedModule, RouteRoutingModule, NgxEchartsModule], declarations: COMPONENTS, }) export class RoutesModule {} diff --git a/web-app/src/app/service/monitor.service.ts b/web-app/src/app/service/monitor.service.ts index 4e05865..b1f195e 100644 --- a/web-app/src/app/service/monitor.service.ts +++ b/web-app/src/app/service/monitor.service.ts @@ -9,6 +9,7 @@ const monitor_uri = "/monitor"; const monitors_uri = "/monitors"; const detect_monitor_uri = "/monitor/detect" const manage_monitors_uri = "/monitors/manage"; +const summary_uri = "/summary"; @Injectable({ providedIn: 'root' @@ -86,4 +87,9 @@ export class MonitorService { public getMonitorMetricData(monitorId: number, metric: string) : Observable> { return this.http.get>(`/monitors/${monitorId}/metrics/${metric}`); } + + public getAppsMonitorSummary() : Observable> { + return this.http.get>(summary_uri); + } + }