Bladeren bron

Angular: extracted chart logic & display into ServerDataChartComponent

Christian Kahlau 3 jaren geleden
bovenliggende
commit
a33b88a9f2

+ 2 - 1
ng/src/app/app.module.ts

@@ -11,9 +11,10 @@ import { HomePageComponent } from './pages/home-page/home-page.component';
 import { FaByTypePipe } from './pipes/fa-by-type.pipe';
 import { ServerDataPageComponent } from './pages/server-data-page/server-data-page.component';
 import { BytePipe } from './pipes/byte.pipe';
+import { ServerDataChartComponent } from './components/server-data-chart/server-data-chart.component';
 
 @NgModule({
-  declarations: [AppComponent, BytePipe, HeaderComponent, HomePageComponent, FaByTypePipe, ServerDataPageComponent],
+  declarations: [AppComponent, BytePipe, FaByTypePipe, HeaderComponent, HomePageComponent, ServerDataChartComponent, ServerDataPageComponent],
   imports: [AppRoutingModule, BrowserModule, FontAwesomeModule, HttpClientModule, NgChartsModule],
   providers: [{ provide: BytePipe, multi: false }],
   bootstrap: [AppComponent]

+ 16 - 0
ng/src/app/components/server-data-chart/server-data-chart.component.html

@@ -0,0 +1,16 @@
+<div class="card mb-2">
+  <div class="card-header d-flex flex-row">
+    <span class="flex-fill">
+      <fa-icon [icon]="parent ?? type.type | faType" class="pe-2"></fa-icon>
+      <span class="text-uppercase">{{ parent ?? type.type }}</span>
+      <ng-container *ngIf="parent"> : {{ type.type }} </ng-container>
+    </span>
+    <span>
+      <span class="badge bd-blue-100">{{ type.start | date: 'YYYY-MM-dd HH:mm:ss' }}</span> -
+      <span class="badge bd-blue-100">{{ type.end | date: 'YYYY-MM-dd HH:mm:ss' }}</span>
+    </span>
+  </div>
+  <div class="card-body">
+    <canvas baseChart type="line" [data]="type.data" [options]="type.options"></canvas>
+  </div>
+</div>

+ 0 - 0
ng/src/app/components/server-data-chart/server-data-chart.component.scss


+ 23 - 0
ng/src/app/components/server-data-chart/server-data-chart.component.spec.ts

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

+ 166 - 0
ng/src/app/components/server-data-chart/server-data-chart.component.ts

@@ -0,0 +1,166 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { ChartData, ChartOptions, TooltipItem } from 'chart.js';
+
+import { BytePipe } from 'src/app/pipes/byte.pipe';
+import { ServerApiService } from 'src/app/services/server-api.service';
+import { deepCopy } from '../../../../../common/util/object-utils';
+
+type ChartConfigTimeUnit = false | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | undefined;
+type ServerDataGraphOptions = ServerDataTypesConfig & {
+  subtypes?: Array<ServerDataGraphOptions>;
+  data?: ChartData<'line', ServerData[], Date>;
+  options: ChartOptions<'line'>;
+  start: Date;
+  end: Date;
+};
+
+const typeDotColors: { [k: string]: string } = {
+  avg: '#cc9a06',
+  peak: '#005299',
+  max: '#941320'
+};
+
+const typeBgColors: { [k: string]: string } = {
+  avg: '#ffe69c',
+  peak: '#cce7ff',
+  max: '#e3a3a9'
+};
+
+@Component({
+  selector: 'app-server-data-chart',
+  templateUrl: './server-data-chart.component.html',
+  styleUrls: ['./server-data-chart.component.scss']
+})
+export class ServerDataChartComponent implements OnInit {
+  private _type!: ServerDataGraphOptions;
+
+  @Input() parent?: string;
+  @Input() server!: ServerConfig;
+  @Input() set type(type: ServerDataTypesConfig) {
+    if (!this._type) {
+      const end = new Date();
+      const start = new Date(end.getTime() - 1000 * 60 * 60 * 4);
+      this._type = deepCopy({
+        ...type,
+        end,
+        start
+      }) as ServerDataGraphOptions;
+    } else {
+      this._type.type = type.type;
+      this._type.subtypes = type.subtypes as ServerDataGraphOptions[];
+    }
+  }
+
+  public get type(): ServerDataGraphOptions {
+    return this._type;
+  }
+
+  constructor(private apiService: ServerApiService, private bytePipe: BytePipe) {}
+
+  ngOnInit(): void {
+    this.updateData();
+  }
+
+  private options(type: ServerDataGraphOptions): ChartOptions<'line'> & any {
+    const end = type.end;
+    const start = type.start;
+
+    const diffHrs = (end.getTime() - start.getTime()) / 1000 / 60 / 60;
+    const timeFormat: {
+      tooltipFormat: string;
+      unit: ChartConfigTimeUnit;
+      stepSize: number;
+    } =
+      diffHrs > 24 * 4
+        ? {
+            tooltipFormat: 'yyyy-MM-dd HH:mm',
+            unit: 'day',
+            stepSize: 1
+          }
+        : diffHrs > 24 * 2
+        ? {
+            tooltipFormat: 'yyyy-MM-dd HH:mm',
+            unit: 'hour',
+            stepSize: 6
+          }
+        : diffHrs > 12
+        ? {
+            tooltipFormat: 'HH:mm:ss',
+            unit: 'hour',
+            stepSize: 1
+          }
+        : {
+            tooltipFormat: 'HH:mm:ss',
+            unit: 'minute',
+            stepSize: 30
+          };
+    return {
+      scales: {
+        xAxis: {
+          display: true,
+          type: 'time',
+          time: {
+            ...timeFormat,
+            displayFormats: {
+              day: 'yyyy-MM-dd',
+              hour: 'yyyy-MM-dd HH:mm',
+              minute: 'HH:mm:ss'
+            }
+          }
+        },
+        yAxis: {
+          beginAtZero: true,
+          type: 'linear',
+          ticks: {
+            callback: (val: string | number) => (typeof val === 'number' && type.type !== 'cpu' ? this.bytePipe.transform(val) : `${val} %`)
+          }
+        }
+      }
+    };
+  }
+
+  private async updateData() {
+    try {
+      const end = this.type.end;
+      const start = this.type.start;
+      const data = await this.apiService.queryServerData(
+        this.server.id,
+        `${this.parent ?? this.type.type}${this.parent ? `:${this.type.type}` : ''}`,
+        start,
+        end
+      );
+      const chartData: ChartData<'line', ServerData[], Date> = {
+        labels: data.map(d => d.time),
+        datasets: data.length
+          ? Object.keys(data[0])
+              .filter(k => k !== 'time')
+              .map(key => ({
+                label: key,
+                data,
+                parsing: { yAxisKey: key, xAxisKey: 'time' },
+                fill: key !== 'max',
+                tension: 0.3,
+                backgroundColor: typeBgColors[key],
+                pointBorderColor: typeDotColors[key],
+                borderColor: typeDotColors[key],
+                pointBackgroundColor: typeBgColors[key],
+                pointRadius: key === 'max' ? 0 : undefined,
+                tooltip: {
+                  callbacks: {
+                    label:
+                      this.type.type !== 'cpu'
+                        ? (item: TooltipItem<'line'> & any) => this.bytePipe.transform(item.raw[key])
+                        : (item: TooltipItem<'line'> & any) => `${(item.raw[key] as number).toFixed(2)} %`
+                  }
+                }
+              }))
+          : []
+      };
+
+      this.type.options = this.options(this.type);
+      this.type.data = chartData;
+    } catch (err) {
+      console.error(err);
+    }
+  }
+}

+ 10 - 34
ng/src/app/pages/server-data-page/server-data-page.component.html

@@ -1,38 +1,14 @@
-<h3 *ngIf="server">{{ server.title }}</h3>
+<ng-container *ngIf="server">
+  <h3>{{ server.title }}</h3>
 
-<ng-container *ngIf="types?.length">
-  <ng-container *ngFor="let type of types">
-    <ng-container *ngIf="type.subtypes?.length; else singleDataType">
-      <div *ngFor="let sub of type.subtypes" class="card mb-2">
-        <div class="card-header d-flex flex-row">
-          <span class="flex-fill">
-            <fa-icon [icon]="type.type | faType" class="pe-2"></fa-icon>
-            <span class="text-uppercase">{{ type.type }}</span
-            >: {{ sub.type }}
-          </span>
-          <span>
-            <span class="badge bd-blue-100">{{ sub.start | date: 'YYYY-MM-dd HH:mm:ss' }}</span> -
-            <span class="badge bd-blue-100">{{ sub.end | date: 'YYYY-MM-dd HH:mm:ss' }}</span>
-          </span>
-        </div>
-        <div class="card-body">
-          <canvas baseChart type="line" [data]="sub.data" [options]="sub.options"></canvas>
-        </div>
-      </div>
+  <ng-container *ngIf="types?.length">
+    <ng-container *ngFor="let type of types">
+      <ng-container *ngIf="type.subtypes?.length; else singleDataType">
+        <app-server-data-chart *ngFor="let sub of type.subtypes" [parent]="type.type" [type]="sub" [server]="server"></app-server-data-chart>
+      </ng-container>
+      <ng-template #singleDataType>
+        <app-server-data-chart [type]="type" [server]="server"></app-server-data-chart>
+      </ng-template>
     </ng-container>
-    <ng-template #singleDataType>
-      <div class="card mb-2">
-        <div class="card-header text-uppercase d-flex flex-row">
-          <span class="flex-fill"> <fa-icon [icon]="type.type | faType" class="pe-2"></fa-icon>{{ type.type }}</span>
-          <span>
-            <span class="badge bd-blue-100">{{ type.start | date: 'YYYY-MM-dd HH:mm:ss' }}</span> -
-            <span class="badge bd-blue-100">{{ type.end | date: 'YYYY-MM-dd HH:mm:ss' }}</span>
-          </span>
-        </div>
-        <div class="card-body">
-          <canvas baseChart type="line" [data]="type.data" [options]="type.options"></canvas>
-        </div>
-      </div>
-    </ng-template>
   </ng-container>
 </ng-container>

+ 4 - 165
ng/src/app/pages/server-data-page/server-data-page.component.ts

@@ -1,35 +1,11 @@
 import { Component, OnDestroy, OnInit } from '@angular/core';
 import { ActivationEnd, Router } from '@angular/router';
-import { ChartData, ChartOptions, TooltipItem } from 'chart.js';
 import { _DeepPartialObject } from 'chart.js/types/utils';
 import 'chartjs-adapter-date-fns';
 import { filter, map, Subscription } from 'rxjs';
 
-import { deepCopy } from '../../../../../common/util/object-utils';
-
-import { BytePipe } from 'src/app/pipes/byte.pipe';
 import { ServerApiService } from 'src/app/services/server-api.service';
 
-type ServerDataGraphOptions = ServerDataTypesConfig & {
-  subtypes?: Array<ServerDataGraphOptions>;
-  data?: ChartData<'line', ServerData[], Date>;
-  options: ChartOptions<'line'>;
-  start: Date;
-  end: Date;
-};
-type ChartConfigTimeUnit = false | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | undefined;
-
-const typeDotColors: { [k: string]: string } = {
-  avg: '#cc9a06',
-  peak: '#005299',
-  max: '#941320'
-};
-
-const typeBgColors: { [k: string]: string } = {
-  avg: '#ffe69c',
-  peak: '#cce7ff',
-  max: '#e3a3a9'
-};
 @Component({
   selector: 'app-server-data-page',
   templateUrl: './server-data-page.component.html',
@@ -39,9 +15,9 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
   private subscriptions: Subscription[] = [];
 
   public server?: ServerConfig;
-  public types?: ServerDataGraphOptions[];
+  public types?: ServerDataTypesConfig[];
 
-  constructor(private apiService: ServerApiService, router: Router, private bytePipe: BytePipe) {
+  constructor(apiService: ServerApiService, router: Router) {
     router.events.subscribe({
       next: event => {
         if (event instanceof ActivationEnd) {
@@ -52,11 +28,11 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
             this.clearPageModel();
           }
           this.subscriptions.push(
-            apiService.serverConfigs$.pipe(map(servers => servers.find(s => s.id === serverID))).subscribe(this.onServerConfig.bind(this)),
+            apiService.serverConfigs$.pipe(map(servers => servers.find(s => s.id === serverID))).subscribe(server => (this.server = server)),
             apiService.serverDataTypes$
               .get(serverID)
               .pipe(filter(types => !!types))
-              .subscribe(this.onServerDataTypes.bind(this))
+              .subscribe(types => (this.types = types))
           );
         }
       }
@@ -65,143 +41,6 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
 
   ngOnInit(): void {}
 
-  onServerConfig(server?: ServerConfig) {
-    this.server = server;
-  }
-
-  onServerDataTypes(types: ServerDataTypesConfig[]) {
-    if (this.server) {
-      const end = new Date();
-      const start = new Date(end.getTime() - 1000 * 60 * 60 * 4);
-      this.types = deepCopy(types).map((origType: ServerDataTypeWithData) => {
-        const type = origType as ServerDataGraphOptions;
-        const oldType = this.types?.find(t => type.type === t.type);
-        if (!type.subtypes) {
-          if (oldType && oldType.data) {
-            type.data = oldType.data;
-            return type;
-          }
-          type.start = start;
-          type.end = end;
-          this.updateData(this.server as ServerConfig, type);
-        } else {
-          type.subtypes.forEach((origSub: ServerDataTypeWithData) => {
-            const sub = origSub as ServerDataGraphOptions;
-            if (oldType) {
-              const oldSub = oldType.subtypes?.find(s => sub.type === s.type) as ServerDataGraphOptions;
-              if (oldSub && oldSub.data) {
-                sub.data = oldSub.data;
-                return;
-              }
-            }
-            sub.start = start;
-            sub.end = end;
-            this.updateData(this.server as ServerConfig, type, sub);
-          });
-        }
-        return type as ServerDataGraphOptions;
-      });
-    }
-  }
-
-  async updateData(server: ServerConfig, type: ServerDataGraphOptions, subType?: ServerDataGraphOptions) {
-    try {
-      const end = subType ? subType.end : type.end;
-      const start = subType ? subType.start : type.start;
-
-      const diffHrs = (end.getTime() - start.getTime()) / 1000 / 60 / 60;
-      const timeFormat: {
-        tooltipFormat: string;
-        unit: ChartConfigTimeUnit;
-        stepSize: number;
-      } =
-        diffHrs > 24 * 4
-          ? {
-              tooltipFormat: 'yyyy-MM-dd HH:mm',
-              unit: 'day',
-              stepSize: 1
-            }
-          : diffHrs > 24 * 2
-          ? {
-              tooltipFormat: 'yyyy-MM-dd HH:mm',
-              unit: 'hour',
-              stepSize: 6
-            }
-          : diffHrs > 12
-          ? {
-              tooltipFormat: 'HH:mm:ss',
-              unit: 'hour',
-              stepSize: 1
-            }
-          : {
-              tooltipFormat: 'HH:mm:ss',
-              unit: 'minute',
-              stepSize: 30
-            };
-
-      const data = await this.apiService.queryServerData(server.id, `${type.type}${subType ? `:${subType.type}` : ''}`, start, end);
-      const chartData: ChartData<'line', ServerData[], Date> = {
-        labels: data.map(d => d.time),
-        datasets: data.length
-          ? Object.keys(data[0])
-              .filter(k => k !== 'time')
-              .map(key => ({
-                label: key,
-                data,
-                parsing: { yAxisKey: key, xAxisKey: 'time' },
-                fill: key !== 'max',
-                tension: 0.3,
-                backgroundColor: typeBgColors[key],
-                pointBorderColor: typeDotColors[key],
-                borderColor: typeDotColors[key],
-                pointBackgroundColor: typeBgColors[key],
-                pointRadius: key === 'max' ? 0 : undefined,
-                tooltip: {
-                  callbacks: {
-                    label:
-                      type.type !== 'cpu'
-                        ? (item: TooltipItem<'line'> & any) => this.bytePipe.transform(item.raw[key])
-                        : (item: TooltipItem<'line'> & any) => `${(item.raw[key] as number).toFixed(2)} %`
-                  }
-                }
-              }))
-          : []
-      };
-      const options: ChartOptions<'line'> & any = {
-        scales: {
-          xAxis: {
-            display: true,
-            type: 'time',
-            time: {
-              ...timeFormat,
-              displayFormats: {
-                day: 'yyyy-MM-dd',
-                hour: 'yyyy-MM-dd HH:mm',
-                minute: 'HH:mm:ss'
-              }
-            }
-          },
-          yAxis: {
-            beginAtZero: true,
-            type: 'linear',
-            ticks: {
-              callback: (val: string | number) => (typeof val === 'number' && type.type !== 'cpu' ? this.bytePipe.transform(val) : `${val} %`)
-            }
-          }
-        }
-      };
-      if (subType) {
-        subType.data = chartData;
-        subType.options = options;
-      } else {
-        type.data = chartData;
-        type.options = options;
-      }
-    } catch (err) {
-      console.error(err);
-    }
-  }
-
   ngOnDestroy(): void {
     this.clearSubscriptions();
   }