[web-app] 仪表盘-监控总览

This commit is contained in:
tomsun28
2021-12-08 12:11:35 +08:00
parent 70af69c12e
commit 6b9e78b187
6 changed files with 295 additions and 132 deletions

View File

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

View File

@@ -1,27 +1,6 @@
<div nz-row [nzGutter]="24" class="pt-lg">
<div nz-col nzXs="24" nzSm="24" nzMd="24" nzLg="18">
<!-- <button nz-button (click)="refresh()" nzType="primary">监控分布</button>-->
<nz-card nzTitle="在网监控" class="pie-card">
<g2-pie
#pie
[hasLegend]="true"
title="在网监控数量"
subTitle="监控数量"
[total]="433"
[valueFormat]="format"
[data]="salesPieData"
height="294"
repaint="false"
(clickItem)="handleClick($event)"
></g2-pie>
</nz-card>
</div>
<div nz-col nzXs="24" nzSm="24" nzMd="12" nzLg="6">
<nz-card nzTitle="监控资源使用情况" class="pie-card">
<div class="text-center">
<g2-water-wave [title]="'已监控License占比'" [percent]="34" [height]="161"></g2-water-wave>
</div>
</nz-card>
<br/>
<div nz-row [nzGutter]="24" style="margin-top: 10px">
<div nz-col nzXs="18" nzSm="18" nzMd="18" nzLg="18" nzOffset="1">
<div echarts [options]="appsCountEChartOption" theme='default' [autoResize]= 'true' [loading]="appsCountLoading" (chartClick)="onChartClick($event)"></div>
</div>
</div>

View File

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

View File

@@ -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} <br/>{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}`);
}
}

View File

@@ -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<Type<void>> = [
DashboardComponent,
@@ -25,7 +24,7 @@ const COMPONENTS: Array<Type<void>> = [
];
@NgModule({
imports: [SharedModule, RouteRoutingModule, G2PieModule, G2WaterWaveModule],
imports: [SharedModule, RouteRoutingModule, NgxEchartsModule],
declarations: COMPONENTS,
})
export class RoutesModule {}

View File

@@ -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<Message<any>> {
return this.http.get<Message<any>>(`/monitors/${monitorId}/metrics/${metric}`);
}
public getAppsMonitorSummary() : Observable<Message<any>> {
return this.http.get<Message<any>>(summary_uri);
}
}