Parcourir la source

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

tomsun28 il y a 4 ans
Parent
commit
d03457fa58

+ 134 - 0
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
+      }
+    }
+  }
+};

+ 4 - 25
web-app/src/app/routes/dashboard/dashboard.component.html

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

+ 2 - 60
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;
 }

+ 144 - 41
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);
   }
 
-  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());
-    }
+  ngOnDestroy(): void {
+    clearInterval(this.interval$);
   }
 
-  format(val: number): string {
-    return `${val.toFixed()}`;
-  }
+  refresh(): void {
+    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;
+          });
 
-  handleClick(data: G2PieClickItem): void {
-    this.msg.info(`${data.item.x} - ${data.item.y}`);
+          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 } });
+      }
+    }
+  }
 }

+ 3 - 4
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<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 {}

+ 6 - 0
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<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);
+  }
+
 }